Спецификатор формата% p нуждается в явном приведении в void * для всех типов, но char * в printf

Я прочитал много ответов об использовании спецификатора формата %p на языке C здесь, в Stack Overflow, но ни один из них не дает объяснения, почему явный приведение в void* необходимо для всех типов, но char*.
Я, конечно, знаю о том, что это требование приведения к или из void* связано с использованием вариационных функций (см. Первый комментарий этого ответа), в то время как необязательно в противном случае.

Вот пример:

int i;    
printf ("%p", &i);

Выдает предупреждение о несовместимости типа и что &i должен быть отброшен в void* (как требуется стандартом, см. Здесь снова).

В то время как этот кусок кода компилируется плавно без каких-либо претензий на литье типов:

char * m = "Hello";    
printf ("%p", m);

Как это происходит, когда char* "освобождается" от этого императива?

PS: Возможно, стоит добавить, что я работаю над архитектурой x86_64, поскольку размер этого типа зависит от него, а с помощью gcc как компилятора на Linux с -W -Wall -std=c11 -pedantic параметры компиляции.

Ответ 1

Для аргументов типа char* нет явного приведения, поскольку char * имеет такое же требование представления и выравнивания, что и void *.

Цитирование C11, глава §6.2.5

Указатель на void должен иметь те же требования к представлению и выравниванию, что и указатель на тип символа. (48) [...]

и сноска 48)

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

Ответ 2

В стандарте C11 6.2.5/28 говорится:

Указатель на void должен иметь те же требования к представлению и выравниванию, что и указатель на тип символа. 48)

со сноской 48:

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

Однако 7.21.6.1 ("Функция fprintf") говорит о %p:

Аргумент должен быть указателем на void.


Это, по-видимому, противоречие. На мой взгляд, разумная интерпретация заключается в том, что цель 6.2.5/28 состоит в том, что void * и char * на самом деле взаимозаменяемы как типы аргументов функции, которые не соответствуют прототипу. (т.е. аргументы для не-прототипированных функций или совпадение многоточия прототипа вариационной функции).

По-видимому, используемый вами компилятор использует аналогичный вид.

Чтобы поддержать это, спецификация типов аргументов в 7.21.6.1, если брать буквально без учета намерения, имеет множество других несоответствий, которые на практике не следует учитывать (например, это говорит, что printf("%lx", -1); корректно определена, но printf("%u", 1); - неопределенное поведение).

Ответ 3

Причиной этого требования является C-стандарт, позволяющий различать представления для указателей на разные типы, с двумя заметными ограничениями:

Следовательно, на некоторых архитектурах int * и char * могут иметь разные представления, например, другого размера, и они могут передаваться по-разному в функции vararg, вызывая int я = 1; printf("%p", &i); int я = 1; printf("%p", &i); и int я = 1; printf("%p", (void*)&i); int я = 1; printf("%p", (void*)&i); вести себя по-другому.

Обратите внимание, однако, что в стандартах Posix указано, что все типы указателей имеют одинаковый размер и представление. Следовательно, в системе Posix printf("%p", &i); должны вести себя так, как ожидалось.

Ответ 4

Из стандарта С# 6.2.5p28

Указатель на void должен иметь те же требования к представлению и выравниванию, что и указатель на тип символа.48) Аналогично, указатели на квалифицированные или неквалифицированные версии совместимых типов должны иметь одинаковые требования к представлению и выравниванию. Все указатели на типы структуры должны иметь одинаковые требования к представлению и согласованию друг с другом. Все указатели на типы объединения должны иметь одинаковые требования к представлению и согласованию друг с другом. Указатели на другие типы не должны иметь одинаковые требования к представлению или выравниванию. [внимание мое]