scanf %p и касты указателей

198
10 марта 2017, 00:13

http://codepad.org/WD1oWXL8

#include <cstdio>
int main(void)
{
  int *p;
  scanf("%p", &p);
  return 0;
}

Line 6: warning: format '%p' expects type 'void**', but argument 2 has type 'int**'

Исправляем на явный каст:

http://codepad.org/TR1XEBKP

scanf("%p", (void**)&p);

Line 6: warning: dereferencing type-punned pointer will break strict-aliasing rules

Пробуем void *:

http://codepad.org/pH4lUnF5

scanf("%p", (void*)&p);

Line 6: warning: format '%p' expects type 'void**', but argument 2 has type 'void*'

В итоге скармливаем двойной каст:

http://codepad.org/1UlOShQJ

scanf("%p", (void**)(void*)&p);

Наконец-то компилируется.

Вопрос:

Зачем именно такая диагностика с указателями была добавлена и какие потенциальные проблемы может вызвать чтение указателя, отличного от void*?

Answer 1

Line 6: warning: format '%p' expects type 'void**', but argument 2 has type 'int**' (хз как красиво выделить желтеньким)

За отображение этой ошибки отвечает флаг -pedantic-errors (он же просто -pedantic). Идем в ман и читаем: Issue all the warnings demanded by strict ISO C and ISO C++; reject all programs that use forbidden extensions, and some other programs that do not follow ISO C and ISO C++. For ISO C, follows the version of the ISO C standard specified by any `-std' option used. бла-бла-бла. Т.е. флаг обеспечивает соответствие кода стандарту для улучшения портируемости на другие платформы/компиляторы.

ISO C++ запрещает неявный каст указателей. Идем в ISO C++ и ищем пруф (т.к. на данный момент стандарт уже перевалил за 1500 стр. делать это я конечно не буду).

Далее, ошибка "Line 6: warning: dereferencing type-punned pointer will break strict-aliasing rules" за неё ответственен флаг -fstrict-aliasing. Этот флаг указывает компилятору, что объект одного типа никогда не может находится по тому же адресу, что и объект другого типа, кроме случаев, когда это похожие объекты, а void и int это непохожие типы.

В итоге вы пришли к правильному решению, сначала кастим int* к void*, потом void* в void**. Хотя я бы написал так:

#include <cstdio>
int main(void)
{
  void* p = nullptr;
  scanf("%p", &p);
  return 0;
}

и кастил бы потом void* куда надо.

Ну и отвечая на вопрос "Зачем именно такая диагностика с указателями была добавлена и какие потенциальные проблемы может вызвать чтение указателя, отличного от void*?". Такая диагностика была добавлена, чтобы программист четко понимал, что, куда и как он кастит (и не мог спихнуть вину на компилятор:)). Про потенциальные проблемы сказать не могу, т.к. ни разу не встречал, чтобы указатели читали scanf'ом, если вы более детально опишите где оно используется, то тогда можно подумать.

P.S. в С такой херни нет, там неявный каст к void разрешен, к слову. P.P.S. "dereferencing type-punned pointer will break strict-aliasing rules" мне кстати получить не удалось на gcc 5.4.0, остальные ошибки успешно повторяются с флагами указанными @mymedia

ссылки:

Флаги g++, -pedantic -fstrict-aliasing https://gcc.gnu.org/onlinedocs/gcc-3.0/gcc_3.html

Стандарт ISO C++: http://open-std.org/Jtc1/sc22/wg21/docs/papers/2016/n4606.pdf

Здесь небольшой тред на тему каста void* http://stackoverflow.com/questions/23145730/why-does-c-forbid-implicit-conversion-of-void

Хабр, про алиасинг и баги https://habrahabr.ru/post/114117/

Немного про разыменование и каст указателей непохожих типов http://stackoverflow.com/questions/26713851/dereferencing-type-punned-pointer-will-break-strict-aliasing-rules-wstrict-ali

Answer 2

Ваш вопрос помечен тагом С++, но все таки для начала пару слов о С:

В языке С указатель типа int * формально может иметь объектное представление, отличное от объектного представления указателя типа void *. В языке С одинаковыми объектными представлениями обладают только все указатели на struct типы (между собой), все указатели на union типы (между собой), а также указатели void * и [signed/unsigned] char * (между собой) (плюс, понятное дело, их cv-квалифицированные вариации). Больше никаких совпадений объектных представлений указателей не гарантируется.

Это означает, что попытка записать значение типа void * в указатель типа int * через переинтерпретацию памяти (type punning) не является формально корректной с точки зрения языка. Язык однозначно говорит, что доступ к объекту типа int * как к lvalue типa void * любым способом, кроме как через union, ведет к неопределенному поведению (см. 6.5/7). Это так называемое правило (или аксиома) strict aliasing.

Поэтому не существует формально портабельного способа сделать scanf("%p" напрямую в указатель типа int *. Даже если обънетные представления этих типов совпадают, и даже если ваши попытки "задушить" предупреждения компилятора увенчались успехом, это все равно не означает, что ваша программа будет работать корректно. Компилятор, опираясь на вышеупомянутые правила strict alising, имеет полное право полагать, что ваш scanf("%p" не модифицирует указатель p (ибо он никак не может легально его модифицировать), т.е. игнорировать информационную зависимость между вызовом scanf и значением p.

Юные "пионэры-практики" из секты "С - это такой портабельный ассемблер" довольно долго умудрялись игнорировать соображения strict aliasing и применяли type punning в своем коде направо и налево, считая это чем-то вроде секретного знания, отличающего их от "теоретиков". Это продолжалось до тех пор пока GCC и другие компиляторы не начали активно использовать в своих оптимизациях ценнейшую информацию, основанную на аксиомах strict aliasing. Вой, поднявшийся в результате из болот "практиков" такого пошиба, окончательно не утих и по сей день. Вот именно от вляпывания в это болото и пытается предохранить вас компилятор своими предупреждениями.

Если вы хотите прочитать значение для вашего p через scanf, то пожалуй единственный цивилизованный путь - это

int *p;
void *v;
scanf("%p", &v);
p = v; // с явным кастом для C++

Или, если уж вам неймется устроить type punning, то - через union

union { int *p; void *v } u;
scanf("%p", &u.v);
p = u.p;

Type punning через union был таки легализован в C99 (TC3, кажется). Разумеется, с оговоркой, что вся ответственность за корректность получаемого объектного представления ложится на вас.

Вышесказанное имеет прямое отношение и к С++, за исключением того, что type punning через union в языке С++ не легализован.

READ ALSO
gtkmm + cmake (windows)

gtkmm + cmake (windows)

Приветствую дамы и господаНе могу автоматически "зацепить" директорию gtkmm с помощью CMake

190
Ошибка памяти C++

Ошибка памяти C++

Выходит ошибка при заполнении A[2][1] те

205
is upper для русского языка

is upper для русского языка

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

208
Не могу найти ошибку в коде c++

Не могу найти ошибку в коде c++

Есть задание: из текста, в котором 2 до 40 слов, в каждом из которых от 1 до 6 строчных латинских букв; между соседними словами – не менее одного...

202