Изменение значения const в C

Я обнаружил, что в следующем фрагменте кода

const int i = 2;  
const int* ptr1= &i;  
int* ptr2 = (int*)ptr1;  
*ptr2 =3;

i значение изменяется до 3. Что мне хотелось бы знать, почему это разрешено. Каковы ситуации, в которых это может стать полезным?

Ответы

Ответ 1

Это разрешено, потому что вы отменили константу ptr1, отбросив ее до указателя, не являющегося константой. Вот почему броски могут быть очень опасными.

Обратите внимание, что некоторые компиляторы, такие как GCC, не позволят вам отбросить статус const таким образом.

Ответ 2

Вы нарушили гарантию на постоянство, играя указатели. Это не гарантируется, что он будет работать все время и может вызвать практически любое поведение в зависимости от системы/ОС/компилятора, в которую вы его выбрали.

Не делай этого.

Или, по крайней мере, не делайте этого, если вы действительно не знаете, что делаете, и даже тогда понимаете, что он ни в коем случае не переносится.

Ответ 3

"Разрешено" противопоставляется "предотвращено", но это также противоположно "запрещенному". Вы видели, что изменение вашего объекта const не предотвращается, но это не означает, что это разрешено.

Изменение объекта const не допускается в смысле "разрешено". Поведение вашей программы не определяется стандартом (см. 6.7.3/5). Так получилось, что в вашей реализации при этом запуске вы увидели значение 3. В другой реализации или в другой день вы можете увидеть другой результат.

Однако это не "предотвращено", потому что с тем, как работает C cast, обнаружение его во время компиляции является проблемой остановки. Обнаружение этого во время выполнения требует дополнительных проверок во всех обращениях к памяти. Стандарт разработан, чтобы не налагать много накладных расходов на реализации.

Причина отказа от const поддерживается вообще, потому что, если у вас есть указатель const на неконстантный объект, этот язык позволяет вам (в обоих смыслах) изменять этот объект. Для этого вам нужно избавиться от спецификатора const. Следствием этого является то, что программисты могут также отбрасывать квалификаторы const от указателей на объекты, которые фактически являются константами.

Здесь (слегка глупый) пример кода, который по этой причине отбрасывает квалификатор const:

typedef struct {
    const char *stringdata;
    int refcount;
} atom;

// returns const, because clients aren't allowed to directly modify atoms,
// just read them
const atom *getAtom(const char *s) {
    atom *a = lookup_in_global_collection_of_atoms(s);
    if (a == 0) {
        // error-handling omitted
        atom *a = malloc(sizeof(atom));
        a->stringdata = strdup(s);
        a->refcount = 1;
        insert_in_global_collection_of_atoms(a);
    } else {
        a->refcount++;
    }
    return a;
}

// takes const, because that what the client has
void derefAtom(const atom *a) {
    atom *tmp = (atom*)a;
    --(tmp->refcount);
    if (tmp->refcount == 0) {
        remove_from_global_collection_of_atoms(a);
        free(atom->stringdata);
        free(atom);
    }
}
void refAtom(const atom *a) {
    ++(((atom*) a)->refcount);
}

Это глупо, потому что лучшим дизайном будет forward-declare atom, чтобы сделать указатели на него полностью непрозрачными и предоставить функцию для доступа к строковым данным. Но C не требует, чтобы вы инкапсулировали все, он позволяет вам возвращать указатели на полностью определенные типы и хочет поддерживать такое использование констант, чтобы представить просмотр только для объекта, который "действительно" модифицируется.

Ответ 4

const действительно означает "readonly".

Как вы узнали, значение объектов const может измениться, но для этого вам нужно использовать коварные методы. И при использовании этих хитрых методов вы вызываете Undefined Поведение.

Ответ 5

Это работает, потому что вы явно удалили const nessy. Хотя ptr1 является указателем на const int, ptr2 является указателем на int, поэтому он может изменяться.

Есть очень мало оснований для этого, но вы можете найти случай, когда он избегает дублирования кода. Например:

const char* letter_at(char* input, int position)
{
    ... stuff ...
    return &found_char;
}

char* editable_letter_at(char* input, int position)
{
    return (char*)(letter_at(input, position));
}

(Пример несколько искалечен из примера С++ в пункте 3 эффективного С++ 3rd)

Ответ 6

Если вы собираетесь отбросить константу в программе на С++, используйте более стильный стиль С++:

int *ptr2 = const_cast<int*>(ptr1);

Если вы столкнулись с проблемами, связанными с этим типом кастинга (вы всегда будете делать), то вы можете найти, где это произошло очень быстро, ища "const_cast", а не пытаться использовать каждую комбинацию под солнцем. Кроме того, это поможет другим нашим, кто может или не может прийти после вас.

Есть только несколько ситуаций, когда я мог видеть, что это полезно. Большинство из них - угловые случаи. Я бы избегал этого любой ценой, если вы разрабатываете на С++.

Ответ 7

C подсказки говорят компилятору, что вы знаете, что делаете, и что вы убедитесь, что все это работает в конце. Если вы используете их, не понимая, что именно вы делаете, вы можете столкнуться с проблемами.

В этом случае компилятор полностью в пределах своих прав поместить i в постоянную память, чтобы этот код сработал при запуске. В противном случае он может работать, как вы видели. Стандарт определяет это как поведение undefined, поэтому буквально все может произойти.

C изначально был предназначен для написания Unix внутри и преднамеренно дает программисту большую свободу в управлении данными, поскольку в ОС, написанном, часто очень полезно писать код, специфичный для конкретной реализации, который делает вещи, которые были бы небезопасными в любых в другом контексте. В обычном коде приложения кастинг должен выполняться с осторожностью.

И не используйте C-style cast в С++. С++ имеет собственное семейство отливок, которые легко найти в коде, и которые часто указывают больше, что делает актерский процесс. В этом конкретном случае вы должны использовать const_cast, который показывает, что вы делаете, и легко найти.

Ответ 8

Потому что C знает, что программисты всегда знают, что делают, и всегда поступают правильно. Вы можете даже сделать это как:

void* ptr2 = (int(*)(void(*)(char*), int[])) ptr1;
(*(int*)ptr2) = 3;

и он даже не будет жаловаться.

Ответ 9

Оператор const int i = 2; означает, что символ/переменная i содержит значение 2, а значение не может быть изменено с помощью i; но нет гарантии, что значение не может быть изменено каким-либо другим способом (как в примере OP).