假设在我的平台sizeof(int)==sizeof(void*)
,我有这样的代码:
printf( "%p", rand() );
请问这个是不确定的,因为传递的值不到位的一个有效的指针的行为%p
?
假设在我的平台sizeof(int)==sizeof(void*)
,我有这样的代码:
printf( "%p", rand() );
请问这个是不确定的,因为传递的值不到位的一个有效的指针的行为%p
?
要在@ larsman的回答(它说,因为你违反了约束,则行为是不确定的)展开,这里是一个实际的C实现,其中sizeof(int) == sizeof(void*)
,但代码不等于printf( "%p", (void*)rand() );
摩托罗拉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
和检索从堆栈中的参数。 但是,你没有通过堆栈上的任何参数! 这将读取未定义堆的垃圾,当它试图将其打印出来,好像它是一个字符串指针,将最有可能你的程序崩溃。
C标准,7.21.6.1,在fprintf
的功能,只是规定
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