#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
这样的完全有效的代码(即,一个整数变成了一个地址/指针,以获取该地址的值),为什么如果{{ 1}}存在吗?*((int*)2000)
,a
和*p
的值(它们在尝试打印**q
之前被打印)? 答案 0 :(得分:3)
*((int*)2000)
不是“完全有效的代码”。将整数转换为指针的结果是实现定义的。在您的实现中,(int*)2000
可能会导致无效的指针。尝试取消引用无效的指针会产生未定义的行为,这意味着任何事情都可能发生。当您在未注释printf
行的情况下运行该程序时,它碰巧会导致分段冲突,因为结果指针指向了不可访问的内存。如果使用了其他整数,则可能导致有效的指针,然后您将看到该内存位置的内容。
在调用函数之前,必须先评估函数调用的所有参数。在评估printf()
的参数时发生了上述错误,因此程序在调用该函数之前就停止了。结果,什么也没打印。
答案 1 :(得分:2)
没问题
表达式(int*)2000
在多个地方使用。您可以使用任意整数并将其转换为指针类型。根据C11标准的第6.3.2.3节,这是允许的:
- 整数可以转换为任何指针类型。除非先前指定,否则结果是实现定义的,可能不是 正确对齐,可能未指向所引用的实体 类型,并且可能是陷阱表示形式
因此,您一定会得到一个指针,但不能保证它是有效的。
潜在问题
然后您将承担第一个风险,因为您将指针类型转换为纯整数:
int y = (int*)2000;
我们已经看到强制转换表达式(int*)2000
是指针类型。根据C标准,在6.3.2.3节中有关指针转换的内容:
- 任何指针类型都可以转换为整数类型。除非先前指定,否则结果是实现定义的。 如果 结果不能以整数类型表示,其行为是 未定义。结果不必在任何值的范围内 整数类型。
因此存在不确定行为的风险。这意味着它可能会致命错误导致程序崩溃或停止。但是,如果您的输出显示值为2000,则意味着结果可以用整数类型表示,并且一切正常,至少可以使用您的特定编译器(这不是通用保证:即使有风险,另一个编译器也可能导致此崩溃低)。
最可能的问题
当您取消对最终声明的注释时,其中会有一个非常危险的表达:
*((int*)2000)
您取消引用通过转换获得的指针。但是,我们在上面已经看到,指针(int*)2000
可能无效。不幸的是,C标准6.5.3.2节对风险非常清楚:
- (...)如果为指针分配了无效值,则一元*运算符的行为是不确定的。
这肯定是:在大多数现代计算机上,操作系统将虚拟内存地址空间分配给进程。然后,操作系统会跟踪有效的地址范围和无效的地址范围。此外,某些操作系统安全性机制使代码的相关地址位置随机化,从而避免可能利用固定地址的黑客攻击。因此,如果您的指针不能指向有效地址(此处为最可能的情况),则该操作很有可能捕获无效的内存访问,从而触发致命错误。
另一个常见的情况是对齐问题:现代CPU对整数有对齐约束。例如,整数不能以奇数地址开头,因为CPU要将其快速加载到其寄存器中会成为一个问题。对齐问题也会导致崩溃。
但是,所有这些仅是未定义行为的潜在示例。另一种情况是,尽管指针无效,但一切似乎都可以正常工作。只是将打印一个垃圾整数值。
结论
代码可能是完全有效的,但仍然导致行为完全未定义。因此,每当您要取消引用指针时,请首先考虑:您可以确定它始终有效吗?
答案 2 :(得分:0)
第二个printf最终为参数编译器时,不会取消引用无效地址,而只是将指针转换回整数。
第三个在传递给printf之前先取消引用它,然后调用UB
但是,这种类型转换在uC开发中很普遍。例如:
*(volatile uint32_t *)(0x40000000U + 0x08000000U + 0x00000000U) = 0x02;
将STM32F3 GPIOC的引脚0设置为备用模式。