Спецификатор %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) являются используется для арифметики (регистры данных). Допустимым соглашением о вызовах для этой архитектуры будет

  1. передайте первые два целых параметра в d0 и d1; передайте остальное в стек.
  2. передайте первые два параметра указателя в a0 и a1; передайте остальное в стек.
  3. передайте все другие типы в стеке, независимо от размера.
  4. параметры передаются в стек помещаются справа налево независимо от тип.
  5. параметры на основе стека выровнены по 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