C - Доступ к неконстантному через константное объявление - PullRequest
6 голосов
/ 08 ноября 2011

Доступ к объекту, не являющемуся const, через объявление const разрешен стандартом C? Например. гарантируется ли следующий код для компиляции и вывода 23 и 42 на стандартной платформе?

Единица перевода A:

int a = 23;
void foo(void) { a = 42; }    

переводческая единица B:

#include <stdio.h>

extern volatile const int a;
void foo(void);

int main(void) {
    printf("%i\n", a);
    foo();
    printf("%i\n", a);
    return 0;
}

В ISO / IEC 9899: 1999 я только что нашел (6.7.3, пункт 5):

Если предпринята попытка изменить объект, определенный с помощью типа с константой, посредством использования lvalue с неконстантным типом, поведение не определено.

Но в приведенном выше случае объект не определен как const (а только объявлен).

UPDATE

Я наконец нашел его в ISO / IEC 9899: 1999.

6.2.7, 2

Все объявления, которые ссылаются на один и тот же объект или функцию, должны иметь совместимый тип; в противном случае поведение не определено.

6.7.3, 9

Для совместимости двух квалифицированных типов оба должны иметь идентичные версия совместимого типа; [...]

Итак, - это неопределенное поведение.

Ответы [ 5 ]

5 голосов
/ 08 ноября 2011

TU A содержит (только) определение a. Таким образом, a на самом деле неконстантный объект, и к нему можно получить доступ из функции A без проблем.

Я почти уверен, что TU B вызывает неопределенное поведение, так как его объявление a не соответствует определению. Лучшая цитата, которую я нашел до сих пор, чтобы подтвердить, что это UB - 6.7.5 / 2:

Каждый декларатор объявляет один идентификатор и утверждает, что когда операнд той же формы, что и декларатор, появляется в выражении, он обозначает функцию или объект с объемом, продолжительностью хранения, и тип, указанный в спецификации объявления.

[Редактировать: с тех пор вопросник нашел правильную ссылку в стандарте, см. Вопрос.]

Здесь объявление в B утверждает, что a имеет тип volatile const int. Фактически объект не имеет (квалифицированного) типа volatile const int, он имеет (квалифицированный) тип int. Нарушение семантики - UB.

На практике произойдет то, что TU A будет скомпилирован, как если бы a не был константным. TU B будет скомпилирован, как если бы a было volatile const int, что означает, что он не будет кэшировать значение a вообще. Таким образом, я ожидал бы, что это сработает, если компоновщик не заметит и не возражает против несовпадающих типов, потому что я не сразу вижу, как TU B может генерировать код, который идет не так. Тем не менее, мой недостаток воображения не совпадает с гарантированным поведением.

AFAIK, в стандарте нет ничего, что говорило бы, что volatile объекты в области видимости файла не могут быть сохранены в совершенно другом банке памяти, чем другие объекты, которые предоставляют различные инструкции для их чтения. Реализация все равно должна быть способна читать нормальный объект, скажем, через указатель volatile, поэтому предположим, например, что «нормальная» инструкция загрузки работает на «специальных» объектах, и она использует это при чтении через указатель к волатильно квалифицированному Но если (в качестве оптимизации) реализация выдает специальную инструкцию для специальных объектов, а специальная инструкция не не работает на обычных объектах, тогда происходит бум. И я думаю, что это ошибка программиста, хотя, признаюсь, я изобрел эту реализацию только 2 минуты назад, поэтому я не могу быть полностью уверен, что она соответствует.

3 голосов
/ 08 ноября 2011

В единице перевода B const запрещает изменять только переменную a внутри самой единицы перевода B.

Изменение этого значения извне (в других единицах перевода) отразится на значении, которое вы видите в B.

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

Обратите внимание, что, если все наоборот (const int a = 23 в A и extern int a в B), вы, скорее всего, столкнетесь с нарушением доступа к памяти в случае попытки изменить a из B, так как a можно поместить в доступную только для чтения область процесса, обычно отображаемую непосредственно из секции .rodata исполняемого файла.

1 голос
/ 08 ноября 2011

Декларация, которая имеет инициализацию, является определением, поэтому ваш объект действительно не является const квалифицированным объектом, и foo имеет все права для его изменения.

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

Ваш второй printf, однако, имеет проблему. Поскольку вы не квалифицировали свою версию B a как volatile, вы не гарантированно увидите модификацию a. Компилятору разрешено оптимизировать и повторно использовать предыдущее значение, которое он мог хранить в регистре.

0 голосов
/ 08 ноября 2011

FWIW: В H & S5 написано (Раздел 4.4.3 Классификаторы типов, стр. 89): «При использовании в контексте, который требует значения, а не указателя, квалификаторы исключаются из типа». Таким образом, const действует только тогда, когда кто-то пытается что-то записать в переменную. В этом случае printf использует a в качестве значения r, а добавленный volatile (ненужный ИМХО) заставляет программу заново прочитать переменную, так что я бы сказал, что программа должна произвести вывод ОП видел изначально, на всех платформах / компиляторах. Я посмотрю на Стандарт и добавлю его, если / когда найду что-то новое.

РЕДАКТИРОВАТЬ: Я не смог найти какое-либо определенное решение этого вопроса в Стандарте (я использовал последний проект для C1X ), поскольку все ссылки на поведение компоновщика концентрируются на идентичности имен. Классификаторы типов во внешних объявлениях, по-видимому, не рассматриваются. Может быть, нам следует направить этот вопрос в Комитет по стандартизации С.

0 голосов
/ 08 ноября 2011

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

...