Подтвердить что ты не робот

Указатель% p только для действительных указателей?

Предположим, что на моей платформе sizeof(int)==sizeof(void*) и у меня есть этот код:

printf( "%p", rand() );

Будет ли это поведение undefined из-за передачи значения, которое не является допустимым указателем вместо %p?

4b9b3361

Ответ 1

Чтобы расширить ответ на @larsman (в котором говорится, что, поскольку вы нарушили ограничение, поведение undefined), здесь выполняется реальная реализация 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 в a0.

В соответствии с этими правилами printf("%p", rand()) передаст строку формата в a0 и параметр случайного числа в d0. С другой стороны, printf("%p", (void*)rand()) передаст строку формата в a0 и параметр случайного указателя в a1.

Структура va_list будет выглядеть так:

struct va_list {
    int d0;
    int d1;
    int a0;
    int a1;
    char *stackParameters;
    int intsUsed;
    int pointersUsed;
};

Первые четыре элемента инициализируются соответствующими входными значениями регистров. 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)).

Функция get_pointer_arg выполняется следующим образом:

void *get_pointer_arg(va_list *ap)
{
    void *p;
    switch (ap->pointersUsed++) {
    case 0: p = ap->a0; break;
    case 1: p = ap->a1; break;
    case 2: p = *(void**)get_other_arg(ap, sizeof(p)); break;
    }
    return p;
}

Функция get_integer_arg выполняется следующим образом:

int get_integer_arg(va_list *ap)
{
    int i;
    switch (ap->intsUsed++) {
    case 0: i = ap->d0; break;
    case 1: i = ap->d1; break;
    case 2: i = *(int*)get_other_arg(ap, sizeof(i)); break;
    }
    return i;
}

И функция get_other_arg выглядит примерно так:

void *get_other_arg(va_list *ap, size_t size)
{
    void *p = ap->stackParameters;
    ap->stackParameters += ((size + 3) & ~3);
    return p;
}

Как отмечалось ранее, вызов printf("%p", rand()) передал строку формата в a0 и случайное целое число в d0. Но когда функция printf выполняется, она увидит формат %p и выполнит va_arg(ap, void*), который будет использовать get_pointer_arg и прочитает параметр a1 вместо d0. Поскольку a1 не был инициализирован, он содержит мусор. Выбранное случайное число игнорируется.

Взяв пример дальше, если у вас есть printf("%p %i %s", rand(), 0, "hello");, это будет вызываться следующим образом:

  • a0= адрес строки формата (первый параметр указателя)
  • a1= адрес строки "hello" (второй параметр указателя)
  • d0= случайное число (первый целочисленный параметр)
  • d1= 0 (второй целочисленный параметр)

Когда функция printf выполняется, она считывает строку формата из a0, как ожидалось. Когда он увидит %p, он будет извлекать указатель из a1 и печатать его, поэтому вы получите адрес строки "hello". Затем он увидит %i и извлечет параметр из d0, чтобы он печатал случайное число. Наконец, он видит %s и извлекает параметр из стека. Но вы не передавали никаких параметров в стек! Это будет читать мусор стека undefined, который, скорее всего, сбой вашей программы, когда он попытается напечатать ее, как если бы это был указатель на строку.

Ответ 2

C стандарт, 7.21.6.1, функция fprintf, указывает только

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

В приложении J.2 это ограничение, и нарушение ограничения вызывает UB.

(Ниже приведены мои предыдущие рассуждения, почему это должен быть UB, который был слишком сложным.)

Этот параграф не описывает, как void* извлекается из ..., но единственный способ, который сам стандарт C предлагает для этой цели, - это 7.16.1.1, макрос va_arg, который предупреждает нас, что

, если тип несовместим с типом фактического следующего аргумента (в соответствии с поощрением по умолчанию), поведение undefined

Если вы читаете 6.2.7, Тип совместимого и составного типа, тогда нет намека на то, что void* и int должны быть совместимы, независимо от их размера. Итак, я бы сказал, что, поскольку va_arg - единственный способ реализовать printf в стандарте C, поведение undefined.

Ответ 3

Да, это undefined. Из С++ 11, 3.7.4.2/4:

Эффект использования недопустимого значения указателя (включая передачу его функции удаления) undefined.

со сноской:

В некоторых реализациях он вызывает системную ошибку выполнения.

Ответ 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