Я столкнулся с более старым кодом в проекте, который объявляет struct в файле H как
struct A {
const int i;
};
Теперь функция, которая создает указатели на структуру A, выглядит так:
struct A * newStructA ( int i ) {
struct B * ptr = malloc(sizeof(struct B));
ptr->i = i;
return (struct A *)ptr;
}
И struct B объявлен в файле C и выглядит так:
struct B {
int i;
};
Вопрос 1: Это даже разрешено в соответствии со стандартом C? Конечно, тип данных i один и тот же, и у struct должно быть такое же расположение памяти, но гарантируется ли это, или модификатор const также может изменить расположение памяти в некоторых системах?
Вопрос 2: Зная, что все struct A * указатели, с которыми имеет дело код, на самом деле являются struct B * указателями, можно ли будет привести указатели обратно к struct B *, а затем изменить значение int? В объявлении сказано, что это const, но я точно знаю, что это не так, поскольку он всегда находится в изменяемой динамической памяти. Или это может иметь последствия, поскольку компилятор C может полагаться на то, что значение является постоянным, поэтому он предполагает, что оно никогда не может измениться, и, таким образом, если две строки кода содержат ptr->i, компилятор может даже не получить значение во второй раз, поскольку как оно могло измениться? если это const? Так как это приведет к очень трудно отслеживаемым ошибкам, которые можно увидеть только при использовании определенного уровня оптимизации.
Вопрос 3: Есть ли лучший способ получить значение const, которое внешний код не может изменить напрямую (или, по крайней мере, никогда не должен пытаться), но внутренний код может измениться, поскольку значение на самом деле вовсе не const? Единственный способ, который я могу придумать, - это полностью скрыть макет структуры (struct A;), но тогда мне нужно предоставить функцию, подобную int getI(struct A * ptr), и всегда получать доступ к значению с помощью этой функции.
Почему struct A вообще существует? Поскольку типы A и B несовместимы, вы не должны обращаться к B через A в первую очередь.
Возможно, «лучший» код для вашей функции newStructA (без хитроумного приведения): struct A temp = { i }; struct A* ptr = malloc(sizeof(struct A)); memcpy(ptr, &temp, sizeof(struct A)); return ptr;. Или вы можете передать адрес составного литерала в memcpy и избавиться от переменной temp: memcpy(ptr, &((struct A) { i }), sizeof(struct A));.
@KamilCuk Но тогда мне пришлось бы дублировать все о вопросах три раза в трех разных вопросах и, таким образом, создавать вопросы, которые на 80% дублируют друг друга.
Проект 2011 года
6.7.3 Квалификаторы типа... 6 Если предпринимается попытка изменить объект, определенный с помощью типа с уточнением const, путем использования lvalue с неконстантный тип, поведение не определено. Если попытка будет сделано для ссылки на объект, определенный с типом volatile-qualified за счет использования lvalue с неизменяемым типом, поведение не определено.133) 133) Это относится к тем объектам, которые ведут себя так, они были определены с помощью уточненных типов, даже если на самом деле они никогда не определенные как объекты в программе (например, объект в отображенном в память входной/выходной адрес).
Я не думаю, что это применимо, поскольку объект получил свой эффективный тип путем присвоения неконстантного типа.
modify an object defined
Какой предмет? Где определили? Когда это было определено?
Разрешено ли вам изменять значение C, которое утверждает, что оно константное, если вы точно знаете, что оно на самом деле непостоянно?
Да.
Это даже разрешено в соответствии со стандартом C?
Да.
это гарантировано или модификатор const также может изменить расположение памяти в некоторых системах?
Возможно, при определении переменной как const переменная обычно помещается в другую область памяти, доступную только для чтения.
Зная, что все указатели на структуру A *, с которыми работает код, на самом деле являются указателями на структуру B *, будет ли разрешено привести указатели обратно к структуре B *, а затем изменить значение int?
Это неясно. Можете ли вы безопасно преобразовать структуру C с неконстантными членами в эквивалентную структуру с константными членами?
Или это может иметь последствия, поскольку компилятор C может полагаться на то, что значение является постоянным, поэтому он предполагает, что оно никогда не может измениться, и, таким образом, если две строки кода содержат ptr->i, компилятор может даже не получить значение во второй раз, как мог бы это изменилось, если это константа?
Да. Связанный https://stackoverflow.com/a/20707255/9072753 .
Есть ли лучший способ получить константное значение, которое внешний код не может изменить напрямую (или, по крайней мере, никогда не должен пытаться), но внутренний код может измениться, поскольку значение на самом деле вовсе не является константным?
Это C. Дух C говорит https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2086.htm :
(1) Trust the programmer.
(2) Don't prevent the programmer from doing what needs to be done.
На мой взгляд, лучше не прятаться, а просто использовать структуру с неконстантными членами. Программист на C в любом случае сможет получить к нему доступ.
Единственный способ, который я могу придумать, - это полностью скрыть макет структуры (struct A;), но тогда мне нужно предоставить такую функцию, как int getI (struct A * ptr)
Да, скрытие чего-либо приведет к накладным расходам во время выполнения. FILE * был с нами всегда, и члены FILE видны для пользовательского кода во многих реализациях. При этом ими никто не пользуется.
Чтобы добиться того, чтобы значение не изменялось внешним кодом, напишите спецификацию вашей библиотеки, что внешний код не должен этого делать.
Чтобы скрыть свой собственный код, используйте средства доступа и позвольте пользователям работать только с указателями на ваши данные с помощью идиомы PIMPL.
Что ж, "Доверяйте программисту" "устарело" согласно C11 и приведенной вами ссылке ;-) А учитывая, что 99,9999% всех ошибок вызваны программистом (а не компилятором, интерпретатором или оборудованием), все, что может предотвратить ошибки в первую очередь, наверное, хорошо. В конце концов, как вы указали, это не помешает программистам делать что-то преднамеренно, это просто помешает им сделать это случайно.
Разве это не должно быть 3 вопроса? Или два?