假设在我的平台sizeof(int)==sizeof(void*)
上,我有这个代码:
printf( "%p", rand() );
这是不确定的行为,因为传递的值不是有效指针而不是%p
?
答案 0 :(得分:20)
要扩展@ larsman的答案(说明因为你违反了约束,行为是未定义的),这里是一个实际的C实现sizeof(int) == sizeof(void*)
,但代码不等同于printf( "%p", (void*)rand() );
< / p>
Motorola 68000处理器有16个寄存器,用于一般计算,但它们不相同。其中八个(名为a0
到a7
)用于访问存储器(地址寄存器),另外八个(d0
到d7
)用于算术(数据寄存器) )。该架构的有效调用约定是
d0
和d1
中的前两个整数参数;将其余部分传递给堆栈。a0
和a1
中的前两个指针参数;将其余部分传递给堆栈。这是一个完全合法的调用约定,类似于许多现代处理器使用的调用约定。
例如,要调用函数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
并从堆栈中检索参数。但是你没有在堆栈上传递任何参数!这将读取未定义的堆栈垃圾,当它尝试打印时,很可能会使程序崩溃,就像它是一个字符串指针一样。
答案 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_arg
和void*
应该兼容,无论其大小如何。所以,我要说因为int
是在标准C 中实现va_arg
的唯一方法,所以行为是未定义的。
答案 2 :(得分:5)
是的,它未定义。从C ++ 11,3.7.4.2/4开始:
使用无效指针值(包括将其传递给释放函数)的效果未定义。
脚注:
在某些实现中,它会导致系统生成的运行时故障。