Спецификатор %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