sha*_*oth 20 c++ pointers reinterpret-cast
假设在我的平台上sizeof(int)==sizeof(void*),我有这个代码:
printf( "%p", rand() );
Run Code Online (Sandbox Code Playgroud)
这是不确定的行为,因为传递一个不是有效指针的值代替%p?
Ray*_*hen 20
为了扩展@ 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;
};
Run Code Online (Sandbox Code Playgroud)
前四个成员使用寄存器的相应输入值进行初始化.的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;
}
Run Code Online (Sandbox Code Playgroud)
该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;
}
Run Code Online (Sandbox Code Playgroud)
而get_other_arg函数是这样的:
void *get_other_arg(va_list *ap, size_t size)
{
void *p = ap->stackParameters;
ap->stackParameters += ((size + 3) & ~3);
return p;
}
Run Code Online (Sandbox Code Playgroud)
如前所述,调用printf("%p", rand())将传入格式字符串a0和随机整数d0.但是当printf函数执行时,它将看到%p格式并执行a 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并从堆栈中检索参数.但是你没有在堆栈上传递任何参数!这将读取未定义的堆栈垃圾,当它尝试打印时,很可能会使程序崩溃,就像它是一个字符串指针一样.
Fre*_*Foo 13
C标准,7.21.6.1,该fprintf功能,仅表示
p参数应该是指针void.
通过附录J.2,这是一个约束,违反约束会导致UB.
(以下是我之前的推理,为什么这应该是UB,这太复杂了.)
该段没有描述如何void*从中检索...,但C标准本身为此目的提供的唯一方法是7.16.1.1,该va_arg宏警告我们
如果type与实际的下一个参数的类型不兼容(根据默认参数提升而提升),则行为未定义
如果您阅读6.2.7,兼容类型和复合类型,那么无论其大小如何,都没有提示void*并且int应该兼容.所以,我要说,因为这va_arg是printf 在标准C中实现的唯一方法,所以行为是未定义的.
是的,这是未定义的.从C++ 11,3.7.4.2/4开始:
使用无效指针值(包括将其传递给释放函数)的效果未定义.
用脚注:
在某些实现中,它会导致系统生成的运行时故障.