%p说明符仅用于有效指针吗?

时间:2012-07-27 12:52:23

标签: c++ pointers reinterpret-cast

假设在我的平台sizeof(int)==sizeof(void*)上,我有这个代码:

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

这是不确定的行为,因为传递的值不是有效指针而不是%p

3 个答案:

答案 0 :(得分:20)

要扩展@ larsman的答案(说明因为你违反了约束,行为是未定义的),这里是一个实际的C实现sizeof(int) == sizeof(void*),但代码不等同于printf( "%p", (void*)rand() ); < / p>

Motorola 68000处理器有16个寄存器,用于一般计算,但它们不相同。其中八个(名为a0a7)用于访问存储器(地址寄存器),另外八个(d0d7)用于算术(数据寄存器) )。该架构的有效调用约定是

  1. 传递d0d1中的前两个整数参数;将其余部分传递给堆栈。
  2. 传递a0a1中的前两个指针参数;将其余部分传递给堆栈。
  3. 无论大小如何,都将所有其他类型传递给堆栈。
  4. 无论类型如何,都会从右向左推送在堆栈上传递的参数。
  5. 基于堆栈的参数在4字节边界上对齐。
  6. 这是一个完全合法的调用约定,类似于许多现代处理器使用的调用约定。

    例如,要调用函数void foo(int i, void *p),您可以i中的d0p中的a0传递。

    请注意,要调用函数void bar(void *p, int i),您还可以i中的d0p中的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指向通过...传递的第一个基于堆栈的参数,intsUsedpointersUsed被初始化为命名参数的数量,这些参数是整数和指针,分别

    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并从堆栈中检索参数。但是你没有在堆栈上传递任何参数!这将读取未定义的堆栈垃圾,当它尝试打印时,很可能会使程序崩溃,就像它是一个字符串指针一样。

答案 1 :(得分:13)

C标准,7.21.6.1,fprintf函数,仅表示

  

p参数应是指向void的指针。

通过附录J.2,这是约束,违反约束会导致UB。

(以下是我之前的理由,为什么这应该是UB,这太复杂了。)

该段落没有描述如何从void*中检索...,但C标准本身为此目的提供的唯一方法是7.16.1.1,{{1宏,它警告我们

  

如果 type 与实际的下一个参数的类型不兼容(根据默认参数提升而提升),则行为未定义

如果您阅读6.2.7,兼容类型和复合类型,则无法提及va_argvoid*应该兼容,无论其大小如何。所以,我要说因为int是在标准C 中实现va_arg 的唯一方法,所以行为是未定义的。

答案 2 :(得分:5)

是的,它未定义。从C ++ 11,3.7.4.2/4开始:

  

使用无效指针值(包括将其传递给释放函数)的效果未定义。

脚注:

  

在某些实现中,它会导致系统生成的运行时故障。