[英]Might casting void* into unsigned long int cause undefined behaviour even where sizeof(void*)==sizeof(unsigned long int)
size_t size_int = sizeof(unsigned long int);
size_t size_ptr = sizeof(void*);
printf("sizeof(unsigned long int): %zu\n", size_int);
printf("sizeof(void*): %zu\n", size_ptr);
if(size_int == size_ptr) {
int a = 0;
void * ptr_a = &a;
// case 1
unsigned long int case_1 = *((unsigned long int*)&ptr_a);
printf("case 1: %lu\n", case_1);
// case 2
unsigned long int case_2 = (unsigned long int)ptr_a;
printf("case 2: %lu\n", case_2);
// case 3
unsigned long int case_3 = 0;
memcpy(&case_3, &ptr_a, sizeof(void*));
printf("case 3: %lu\n", case_3);
// case 4
void *ptr_b = NULL;
memcpy(&ptr_b, &case_3, sizeof(void*));
int *ptr_c = (int*)ptr_b;
*ptr_c = 5;
printf("case 5: %i\n", a);
}
事实上,我知道 C99 中有 uintptr_t 和 intptr_t 。 但是,出于教育目的,我想问一些问题。 在开始之前,我知道这是一种不好的做法,绝不应该以这种方式进行。
Q1。 案例 1 会导致未定义的行为吗? 安全吗? 如果不是,为什么? 如果它是安全的,是否可以保证“case_1”变量与 unsigned long int 具有完全相同的地址?
Q2。 案例 2 同上。
Q3。 案例 3 同上。
Q4。 案例 4 同上。
unsigned long int case_1 = *((unsigned long int*)&ptr_a);
忽略指针 vs integer 大小问题,由于严格的别名违规,这仍然是未定义的行为。 什么是严格的别名规则? void*
object 所在的 memory 位置无法取消引用为unsigned long
。 这反过来又会导致在优化等过程中错误地生成机器代码,尤其是当代码被划分为多个翻译单元时。 所以这是有道理的担忧,而不仅仅是理论上的“语言律师”。
由于 alignment 的问题,至少在理论上,也可能存在未定义的行为。 在实践中,如果指针和整数保持相同的大小,我真的不知道 alignment 将如何成为问题。
从理论上讲,可能存在陷阱表示,无论是在unsigned long
(这反过来需要一个奇异的 1 的补码或有符号幅度系统),还是在指针类型本身中。 某些硬件可能对某些地址具有陷阱表示,理论上您可能会在此类系统上遇到硬件异常,尽管可能只是在从 integer 转到指针时。
unsigned long int case_2 = (unsigned long int)ptr_a;
这是明确定义的——我们总是可以从指针转换为整数并返回。 但同样存在 object 大小以及 alignment 大小的问题 - 特别是在从整数到指针时。
memcpy(&case_3, &ptr_a, sizeof(void*));
除了相同的大小和 alignment 问题之外,这是有效的代码。 而C对指针的二进制表示没有任何要求,超出了标准的scope。
memcpy(&ptr_b, &case_3, sizeof(void*));
与 3) 相同的问题。
虽然在不同类型之间转换指针时可能会发生未定义的行为,但您可以将void *
指针转换为/来自任何其他指针类型。
case # 是未定义的行为吗? 安全吗? 如果不是,为什么?
unsigned long int case_1 = *((unsigned long int*)&ptr_a);
这是未定义的行为。 您正在使用unsigned long int
类型访问void *
值。 因为unsigned long int
与void*
不兼容,所以您打破了严格的别名。 见C11 6.5p7 。
unsigned long int case_2 = (unsigned long int)ptr_a;
这可能是未定义的行为。 见C11 6.3.2.3p6 。 虽然它说Any pointer type may be converted to an integer type
,但它还指出If the result cannot be represented in the integer type, the behavior is undefined
。 因此,在unsigned long
有 32 位但void *
有 64 位的架构上,这可能是未定义的行为。 结果在任何情况下都是实现定义的。
unsigned long int case_3 = 0; memcpy(&case_3, &ptr_a, sizeof(void*));
当sizeof(void*) > sizeof(unsigned long)
时,这显然是未定义的行为。
printf("case 3: %lu\n", case_3);
当case_3
是陷阱表示时,这可能是未定义的行为。 IE。 如果case_3
的内容没有呈现有效的“unsigned long int”object,则从该 object 读取执行陷阱。 但是在当今的架构上,任何位模式都对unsigned long
有效,因此它将导致一些实现定义的模式。
memcpy(&ptr_b, &case_3, sizeof(void*));
这等于ptr_b = case_3
并且是有效的。
C 语言参考(或更准确地说是 C11 的 n1570 草案)在 6.3.2.3 Conversions / Pointers §6 中说:
任何指针类型都可以转换为 integer 类型。 除非前面指定,结果是实现定义的。 如果结果无法以 integer 类型表示,则行为未定义。 结果不必在任何 integer 类型的值范围内。
因此,即使sizeof(void*)==sizeof(unsigned long int)
如果由于任何原因无法表示结果,它也可能是未定义的行为。 原因可能是:
unsigned long
类型中的填充位导致较少的表示值对于常见的架构(实际上我所知道的),将指针转换为无符号长整数会给出具有完全相同位的 memory 地址,并且在任何 integer 类型中都没有填充,因此不会发生未定义的行为。 但是这个标准非常保守……
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.