此代码出了什么问题,该代码在将整数转换为整数指针后打印在获得的地址上的值

时间:2018-08-23 19:36:02

标签: c pointers termination

#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这样的完全有效的代码(即,一个整数变成了一个地址/指针,以获取该地址的值),为什么如果{{ 1}}存在吗?
  2. 为什么该代码在终止程序之前不打印*((int*)2000)a*p的值(它们在尝试打印**q之前被打印)?

3 个答案:

答案 0 :(得分:3)

  1. *((int*)2000)不是“完全有效的代码”。将整数转换为指针的结果是实现定义的。在您的实现中,(int*)2000可能会导致无效的指针。尝试取消引用无效的指针会产生未定义的行为,这意味着任何事情都可能发生。当您在未注释printf行的情况下运行该程序时,它碰巧会导致分段冲突,因为结果指针指向了不可访问的内存。如果使用了其他整数,则可能导致有效的指针,然后您将看到该内存位置的内容。

  2. 在调用函数之前,必须先评估函数调用的所有参数。在评估printf()的参数时发生了上述错误,因此程序在调用该函数之前就停止了。结果,什么也没打印。

答案 1 :(得分:2)

没问题

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

  
      
  1. 整数可以转换为任何指针类型。除非先前指定,否则结果是实现定义的,可能不是   正确对齐,可能未指向所引用的实体   类型,并且可能是陷阱表示形式
  2.   

因此,您一定会得到一个指针,但不能保证它是有效的。

潜在问题

然后您将承担第一个风险,因为您将指针类型转换为纯整数:

int y = (int*)2000;

我们已经看到强制转换表达式(int*)2000是指针类型。根据C标准,在6.3.2.3节中有关指针转换的内容:

  
      
  1. 任何指针类型都可以转换为整数类型。除非先前指定,否则结果是实现定义的。 如果   结果不能以整数类型表示,其行为是   未定义。结果不必在任何值的范围内   整数类型。
  2.   

因此存在不确定行为的风险。这意味着它可能会致命错误导致程序崩溃或停止。但是,如果您的输出显示值为2000,则意味着结果可以用整数类型表示,并且一切正常,至少可以使用您的特定编译器(这不是通用保证:即使有风险,另一个编译器也可能导致此崩溃低)。

最可能的问题

当您取消对最终声明的注释时,其中会有一个非常危险的表达:

*((int*)2000)

您取消引用通过转换获得的指针。但是,我们在上面已经看到,指针(int*)2000可能无效。不幸的是,C标准6.5.3.2节对风险非常清楚:

  
      
  1. (...)如果为指针分配了无效值,则一元*运算符的行为是不确定的。
  2.   

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

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

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

结论

代码可能是完全有效的,但仍然导致行为完全未定义。因此,每当您要取消引用指针时,请首先考虑:您可以确定它始终有效吗?

答案 2 :(得分:0)

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

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

但是,这种类型转换在uC开发中很普遍。例如:

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

将STM32F3 GPIOC的引脚0设置为备用模式。