这段代码有什么问题,它打印将整数转换为整数指针后获得的地址处的值
What's wrong with this code which prints the value at address obtained after converting an integer to an integer pointer
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
int** q = &p;
printf("\n%d %d %d", a, *p, **q);
int y = (int*)2000;
printf("%d", y);
/*
printf("\n%d %d %d %d", a, *p, **q, *((int*)2000)); //Error line.
*/
return 0;
}
此代码编译并运行。但是,如果您取消对注释代码的注释,代码仍会编译,但会在打印最后一个 printf
命令的结果之前终止。
我的两个问题是:
- 如果
*((int*)2000)
是像 (int*)2000
一样完全有效的代码(即,将整数转换为 address/pointer 以获取该地址处的值),为什么程序在如果 *((int*)2000)
存在则结束?
- 为什么这段代码在终止程序之前不打印
a
、*p
和 **q
的值(它们是在尝试打印 *((int*)2000))
之前打印的) ?
第二个 printf 当参数最终确定时,编译器不会取消对无效地址的引用,它只是将指针转换回整数。
第三个在传递给 printf 之前取消引用它,然后您调用 UB
但是这样的转换在 uC 开发中很常见。例如:
*(volatile uint32_t *)(0x40000000U + 0x08000000U + 0x00000000U) = 0x02;
将STM32F3 GPIOC的引脚0设置为交替模式。
*((int*)2000)
不是 "perfectly valid code"。将整数转换为指针的结果是实现定义的。在您的实现中,(int*)2000
很可能会导致指针无效。尝试取消引用无效指针会产生未定义的行为,这意味着任何事情都可能发生。当您 运行 取消注释 printf
行的程序时,它恰好导致了段冲突,因为生成的指针指向了不可访问的内存。如果您使用了一些其他整数,它可能会产生一个有效的指针,然后您会看到该内存位置的内容。
函数调用的所有参数都必须在调用函数之前计算。上面的错误是在计算 printf()
的参数时发生的,因此程序在调用函数之前停止。因此,没有打印任何内容。
没问题
表达式 (int*)2000
在多处使用。您采用任意整数并将其转换为指针类型。根据 C11 标准第 6.3.2.3 节,这是允许的:
- An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not
be correctly aligned, might not point to an entity of the referenced
type, and might be a trap representation
所以你肯定会得到一个指针,但你不能保证它是有效的。
潜在问题
然后你冒第一个风险,因为你将指针类型转换为普通整数:
int y = (int*)2000;
我们已经看到转换表达式 (int*)2000
是一个指针类型。根据 C 标准,在 6.3.2.3 节中关于指针转换:
- Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the
result cannot be represented in the integer type, the behavior is
undefined. The result need not be in the range of values of any
integer type.
因此存在未定义行为的风险。这意味着它可能会因致命错误而崩溃或停止程序。但是如果你的输出显示值 2000,这意味着结果可以用整数类型表示并且一切都很好,至少对于你的特定编译器(这不是一个普遍的保证:另一个编译器可能会导致崩溃,即使风险低)。
最有可能的问题
当您取消注释最后的语句时,其中有一个非常危险的表达式:
*((int*)2000)
您取消引用通过转换获得的指针。但是,我们在上面已经看到,指针 (int*)2000
可能无效。不幸的是,C 标准第 6.5.3.2 节非常清楚风险:
- (...) If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.
这当然是:在大多数现代计算机上,操作系统为进程分配一个虚拟内存地址 space。然后操作系统会跟踪有效地址范围和无效地址范围。另外,一些OS安全机制让你的代码的相关地址位置是随机的,这样可以避免黑客攻击,可以利用固定地址。因此,如果您的指针未指向有效地址(此处最可能的情况),则操作很有可能会捕获无效的内存访问,从而触发致命错误。
另一个常见的情况是对齐问题:现代 CPUs 对整数有对齐约束。例如,整数不能从奇数地址开始,因为 CPU 将其快速加载到其寄存器中将是一个问题。对齐问题也会导致崩溃。
但这一切只是未定义行为的潜在例子。另一种情况,可能是一切似乎都工作正常,尽管指针无效。只是会打印一个垃圾整数值。
结论
代码可以完全有效,但仍然会导致完全未定义的行为。所以,每当你想取消引用一个指针时,首先要考虑:你能确定它总是有效的吗?
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
int** q = &p;
printf("\n%d %d %d", a, *p, **q);
int y = (int*)2000;
printf("%d", y);
/*
printf("\n%d %d %d %d", a, *p, **q, *((int*)2000)); //Error line.
*/
return 0;
}
此代码编译并运行。但是,如果您取消对注释代码的注释,代码仍会编译,但会在打印最后一个 printf
命令的结果之前终止。
我的两个问题是:
- 如果
*((int*)2000)
是像(int*)2000
一样完全有效的代码(即,将整数转换为 address/pointer 以获取该地址处的值),为什么程序在如果*((int*)2000)
存在则结束? - 为什么这段代码在终止程序之前不打印
a
、*p
和**q
的值(它们是在尝试打印*((int*)2000))
之前打印的) ?
第二个 printf 当参数最终确定时,编译器不会取消对无效地址的引用,它只是将指针转换回整数。
第三个在传递给 printf 之前取消引用它,然后您调用 UB
但是这样的转换在 uC 开发中很常见。例如:
*(volatile uint32_t *)(0x40000000U + 0x08000000U + 0x00000000U) = 0x02;
将STM32F3 GPIOC的引脚0设置为交替模式。
*((int*)2000)
不是 "perfectly valid code"。将整数转换为指针的结果是实现定义的。在您的实现中,(int*)2000
很可能会导致指针无效。尝试取消引用无效指针会产生未定义的行为,这意味着任何事情都可能发生。当您 运行 取消注释printf
行的程序时,它恰好导致了段冲突,因为生成的指针指向了不可访问的内存。如果您使用了一些其他整数,它可能会产生一个有效的指针,然后您会看到该内存位置的内容。函数调用的所有参数都必须在调用函数之前计算。上面的错误是在计算
printf()
的参数时发生的,因此程序在调用函数之前停止。因此,没有打印任何内容。
没问题
表达式 (int*)2000
在多处使用。您采用任意整数并将其转换为指针类型。根据 C11 标准第 6.3.2.3 节,这是允许的:
- An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation
所以你肯定会得到一个指针,但你不能保证它是有效的。
潜在问题
然后你冒第一个风险,因为你将指针类型转换为普通整数:
int y = (int*)2000;
我们已经看到转换表达式 (int*)2000
是一个指针类型。根据 C 标准,在 6.3.2.3 节中关于指针转换:
- Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.
因此存在未定义行为的风险。这意味着它可能会因致命错误而崩溃或停止程序。但是如果你的输出显示值 2000,这意味着结果可以用整数类型表示并且一切都很好,至少对于你的特定编译器(这不是一个普遍的保证:另一个编译器可能会导致崩溃,即使风险低)。
最有可能的问题
当您取消注释最后的语句时,其中有一个非常危险的表达式:
*((int*)2000)
您取消引用通过转换获得的指针。但是,我们在上面已经看到,指针 (int*)2000
可能无效。不幸的是,C 标准第 6.5.3.2 节非常清楚风险:
- (...) If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.
这当然是:在大多数现代计算机上,操作系统为进程分配一个虚拟内存地址 space。然后操作系统会跟踪有效地址范围和无效地址范围。另外,一些OS安全机制让你的代码的相关地址位置是随机的,这样可以避免黑客攻击,可以利用固定地址。因此,如果您的指针未指向有效地址(此处最可能的情况),则操作很有可能会捕获无效的内存访问,从而触发致命错误。
另一个常见的情况是对齐问题:现代 CPUs 对整数有对齐约束。例如,整数不能从奇数地址开始,因为 CPU 将其快速加载到其寄存器中将是一个问题。对齐问题也会导致崩溃。
但这一切只是未定义行为的潜在例子。另一种情况,可能是一切似乎都工作正常,尽管指针无效。只是会打印一个垃圾整数值。
结论
代码可以完全有效,但仍然会导致完全未定义的行为。所以,每当你想取消引用一个指针时,首先要考虑:你能确定它总是有效的吗?