Спецификатор %p предназначен только для допустимых указателей?
предположим, на моей платформе sizeof(int)==sizeof(void*)
и у меня есть этот код:
printf( "%p", rand() );
будет ли это неопределенное поведение из-за передачи значения, которое не является допустимым указателем вместо %p
?
4 ответов
чтобы расширить ответ @larsman (который говорит, что, поскольку вы нарушили ограничение, поведение не определено), вот фактическая реализация C, где sizeof(int) == sizeof(void*)
, но код не эквивалентен printf( "%p", (void*)rand() );
процессор Motorola 68000 имеет 16 регистров, которые используются для общих вычислений, но они не эквивалентны. Восемь из них (по имени a0
через a7
) используются для доступа к памяти (регистры адресов) и остальные восемь (d0
через d7
) являются используется для арифметики (регистры данных). Допустимым соглашением о вызовах для этой архитектуры будет
- передайте первые два целых параметра в
d0
иd1
; передайте остальное в стек. - передайте первые два параметра указателя в
a0
иa1
; передайте остальное в стек. - передайте все другие типы в стеке, независимо от размера.
- параметры передаются в стек помещаются справа налево независимо от тип.
- параметры на основе стека выровнены по 4-байтовым границам.
это совершенно законное соглашение о вызовах, подобное соглашениям о вызовах, используемым многими современными процессорами.
например, для вызова функции void foo(int i, void *p)
, передать i
на d0
и p
на a0
.
обратите внимание, что для вызова функции void bar(void *p, int i)
, вы бы тоже прошли i
на d0
и p
in a0
.
по этим правилам,printf("%p", rand())
передаст строку формата в a0
и параметр случайного числа в d0
. С другой стороны,--27--> передаст строку формата в a0
и параметр случайного указателя в a1
.
на va_list
структура будет выглядеть так:
struct va_list {
int d0;
int d1;
int a0;
int a1;
char *stackParameters;
int intsUsed;
int pointersUsed;
};
первые четыре элемента инициализируются соответствующими значениями записи регистров. The stackParameters
указывает на первый стек на основе параметры, передаваемые через ...
и intsUsed
и pointersUsed
инициализируются количество именованных параметров, которые являются целыми числами и указателями, соответственно.
на va_arg
макрос-это встроенный компилятор, который генерирует другой код на основе ожидаемого типа параметра.
- если тип параметра является указателем, то
va_arg(ap, T)
увеличивается до(T*)get_pointer_arg(&ap)
. - если тип параметра является целым числом, то
va_arg(ap, T)
расширяется(T)get_integer_arg(&ap)
. - если тип параметра что-то еще, то
va_arg(ap, T)
увеличивается до*(T*)get_other_arg(&ap, sizeof(T))
.
на
стандарт C, 7.21.6.1,, просто
p
аргумент должен быть указателем наvoid
.
по приложению J. 2, это ограничения, и нарушение ограничения вызывает UB.
(ниже приведены мои предыдущие рассуждения, почему это должен быть UB, который был слишком сложным.)
этот абзац не описывает, как void*
извлекается из ...
, но единственный способ, который сам стандарт C предлагает для этой цели, - это 7.16.1.1,va_arg
макрос, который предупреждает нас о том, что
если тип не совместим с типом фактического следующего аргумента (как продвигается в соответствии с промо-акциями аргументов по умолчанию), поведение не определено
если Вы читаете 6.2.7, совместимый тип и составной тип, то нет никакого намека на то, что void*
и int
должны быть совместимы, независимо от их размера. Итак, я бы сказал, что с va_arg
- это единственный способ реализации printf
в стандартном C, поведение не определено.
Да, это неопределенно. Из C++11, 3.7.4.2/4:
эффект использования недопустимого значения указателя (включая передачу его функции освобождения) не определен.
в сноске:
в некоторых реализациях это вызывает системную ошибку времени выполнения.
%p-это просто спецификация выходного формата для printf. Ему не нужно разыменовывать или проверять указатель каким-либо образом, хотя некоторые компиляторы выдают предупреждение, если тип не является указателем:
int main(void)
{
int t = 5;
printf("%p\n", t);
}
компиляции предупреждение:
warning: format ‘%p’ expects argument of type ‘void*’, but argument 2 has type ‘int’ [-Wformat]
выходы:
0x5