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

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


Я хочу сделать код записи, где указатель установлен на конкретный адрес памяти, например 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 (кл) говорит "инициализация int* отличается уровнем косвенности от int".

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


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

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

int* p = 0;

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

1 ответ

Решение

Нет, это недопустимый 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>

Другие вопросы по тегам