#cpp #gcc #language_lawyer
http://codepad.org/WD1oWXL8 #includeint 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*?
Ответы
Ответ 1
Ваш вопрос помечен тагом С++, но все таки для начала пару слов о С: В языке С указатель типа 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 в языке С++ не легализован.Ответ 2
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**. Хотя я бы написал так: #includeint 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* https://stackoverflow.com/questions/23145730/why-does-c-forbid-implicit-conversion-of-void Хабр, про алиасинг и баги https://habrahabr.ru/post/114117/ Немного про разыменование и каст указателей непохожих типов https://stackoverflow.com/questions/26713851/dereferencing-type-punned-pointer-will-break-strict-aliasing-rules-wstrict-ali
Комментариев нет:
Отправить комментарий