这段代码有什么问题,它打印将整数转换为整数指针后获得的地址处的值

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 命令的结果之前终止。

我的两个问题是:

  1. 如果 *((int*)2000) 是像 (int*)2000 一样完全有效的代码(即,将整数转换为 address/pointer 以获取该地址处的值),为什么程序在如果 *((int*)2000) 存在则结束?
  2. 为什么这段代码在终止程序之前不打印 a*p**q 的值(它们是在尝试打印 *((int*)2000)) 之前打印的) ?

第二个 printf 当参数最终确定时,编译器不会取消对无效地址的引用,它只是将指针转换回整数。

第三个在传递给 printf 之前取消引用它,然后您调用 UB

但是这样的转换在 uC 开发中很常见。例如:

*(volatile uint32_t *)(0x40000000U + 0x08000000U + 0x00000000U) = 0x02;

将STM32F3 GPIOC的引脚0设置为交替模式。

  1. *((int*)2000) 不是 "perfectly valid code"。将整数转换为指针的结果是实现定义的。在您的实现中,(int*)2000 很可能会导致指针无效。尝试取消引用无效指针会产生未定义的行为,这意味着任何事情都可能发生。当您 运行 取消注释 printf 行的程序时,它恰好导致了段冲突,因为生成的指针指向了不可访问的内存。如果您使用了一些其他整数,它可能会产生一个有效的指针,然后您会看到该内存位置的内容。

  2. 函数调用的所有参数都必须在调用函数之前计算。上面的错误是在计算 printf() 的参数时发生的,因此程序在调用函数之前停止。因此,没有打印任何内容。

没问题

表达式 (int*)2000 在多处使用。您采用任意整数并将其转换为指针类型。根据 C11 标准第 6.3.2.3 节,这是允许的:

  1. 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 节中关于指针转换:

  1. 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 节非常清楚风险:

  1. (...) If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

这当然是:在大多数现代计算机上,操作系统为进程分配一个虚拟内存地址 space。然后操作系统会跟踪有效地址范围和无效地址范围。另外,一些OS安全机制让你的代码的相关地址位置是随机的,这样可以避免黑客攻击,可以利用固定地址。因此,如果您的指针未指向有效地址(此处最可能的情况),则操作很有可能会捕获无效的内存访问,从而触发致命错误。

另一个常见的情况是对齐问题:现代 CPUs 对整数有对齐约束。例如,整数不能从奇数地址开始,因为 CPU 将其快速加载到其寄存器中将是一个问题。对齐问题也会导致崩溃。

但这一切只是未定义行为的潜在例子。另一种情况,可能是一切似乎都工作正常,尽管指针无效。只是会打印一个垃圾整数值。

结论

代码可以完全有效,但仍然会导致完全未定义的行为。所以,每当你想取消引用一个指针时,首先要考虑:你能确定它总是有效的吗?