Проблемы "Указатель от целого числа / целое число от указателя без приведения" - PullRequest
0 голосов
/ 05 сентября 2018

Этот вопрос предназначен для записи часто задаваемых вопросов для всех инициализаций / присваиваний между целочисленными и указательными проблемами.


Я хочу написать код, в котором указатель установлен на определенный адрес памяти, например, 0x12345678. Но при компиляции этого кода с помощью компилятора gcc я получаю «предупреждение / ошибки инициализации делает указатель из целого числа без приведения»:

int* p = 0x12345678;

Аналогично, этот код выдает "инициализация делает целое число из указателя без приведения":

int* p = ...;
int i =  p;

Если я сделаю то же самое вне строки объявления переменной, сообщение будет таким же, но вместо «инициализация» будет указано «присваивание»:

p = 0x12345678; // "assignment makes pointer from integer without a cast"
i = p;          // "assignment makes integer from pointer without a cast"

Тесты с другими популярными компиляторами также выдают сообщения об ошибках / предупреждения:

  • clang говорит «несовместимое преобразование целого числа в указатель»
  • icc говорит, что «значение типа int не может использоваться для инициализации объекта типа int*»
  • MSVC (cl) говорит, что «инициализация int* отличается по уровням косвенности от int».

Вопрос: Приведенные выше примеры действительны C?


И дополнительный вопрос:

Это не дает никаких предупреждений / ошибок:

int* p = 0;

Почему бы и нет?

1 Ответ

0 голосов
/ 05 сентября 2018

Нет, это недопустимый C и никогда не был допустимым C. Эти примеры являются так называемыми нарушением ограничения стандарта.

Стандарт не позволяет инициализировать / назначить указатель на целое число или целое число на указатель. Вам нужно вручную принудительно преобразовать тип с помощью приведения:

int* p = (int*) 0x1234;

int i = (int)p;

Если вы не используете приведение, код не является допустимым C, и вашему компилятору не разрешено пропускать код без отображения сообщения. В частности, это регулируется правилами простого назначения , C17 6.5.16.1 §1:

6.5.16.1 Простое назначение

Ограничения

Должно быть одно из следующего:

  • левый операнд имеет атомарный, квалифицированный или неквалифицированный арифметический тип, а правый имеет арифметический тип;
  • левый операнд имеет атомарную, квалифицированную или неквалифицированную версию структуры или типа объединения совместим с типом права;
  • левый операнд имеет атомарный, квалифицированный или неквалифицированный тип указателя и (учитывая тип левый операнд будет иметь после преобразования lvalue) оба операнда являются указателями на квалифицированные или неквалифицированные версии совместимых типов, а тип, на который указывает левый, имеет все классификаторы типа, на который указывает право;
  • левый операнд имеет атомарный, квалифицированный или неквалифицированный тип указателя и (с учетом типа левый операнд будет иметь после преобразования lvalue) один операнд является указателем на тип объекта, а другой - указатель на квалифицированную или неквалифицированную версию void, а тип указывает на слева - все классификаторы типа, на который указывает справа;
  • левый операнд является атомарным, квалифицированным или неквалифицированным указателем, а правый - нулевым указателем постоянная; или
  • левый операнд имеет тип атомарный, квалифицированный или неквалифицированный _Bool, а правый - указатель.

В случае int* p = 0x12345678; левый операнд является указателем, а правый - арифметическим.
В случае int i = p; левый операнд является арифметическим типом, а правый - указателем.
Ни один из них не соответствует ни одному из указанных выше ограничений.

Что касается того, почему int* p = 0; работает, это особый случай. Левый операнд является указателем, а правый - константой нулевого указателя . Дополнительная информация о разнице между нулевыми указателями, константами нулевых указателей и макросом NULL .


Некоторые примечания:

  • Если вы назначаете необработанный адрес указателю, указатель, вероятно, должен быть квалифицирован volatile, учитывая, что он указывает на что-то вроде аппаратного регистра или расположения EEPROM / Flash-памяти, которое может изменить его содержимое во время выполнения.

  • Преобразование указателя в целое число никоим образом не гарантирует работу даже с приведением. Стандарт (C17 6.3.2.3 §5 и §6 гласит):

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

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

    Информационная нога:

    68) Функции отображения для преобразования указателя в целое число или целое число в указатель предназначены для согласования со структурой адресации среды выполнения.

    Кроме того, адрес указателя может быть больше, чем тот, который уместится внутри int, как в случае большинства 64-битных систем. Поэтому лучше использовать uintptr_t из <stdint.h>

...