【问题标题】:Why does this C code snippet not result in a segmentation fault?为什么这个 C 代码片段不会导致分段错误?
【发布时间】:2016-10-25 15:29:54
【问题描述】:
int main(int argc, char **argv) {
    unsigned int ptr1 = *((unsigned int *)(argv[1]));
    printf("ptr1 = 0x%x\n", ptr1);
    exit(0);
}

这是视频教程中的代码 sn-p。我不确定我是否理解为什么代码在运行时不会导致分段错误。

(unsigned int *)(argv[1]) 这看起来像传递给程序的第一个参数被强制转换为无符号整数。因此,如果参数是“AAAA”,那么“0x41414141”现在是指向内存中某个位置的指针。

然后当我们这样做时 - *((unsigned int *)(argv[1])),我们不是在推迟地址 0x41414141 指向的值吗?据我所知,该进程可能无法访问此地址。那么为什么没有出现分段错误呢?程序的输出是 -

ptr1 = 0x41414141

我已经用 gcc 在 Linux 上编译了这个程序。

【问题讨论】:

  • argv[1] 持有指向char 数据的指针,您将该指针(而不是它指向的内容)转换为指向unsigned int 数据的指针,然后取消引用 that。您没有取消引用 0x41414141。您正在取消引用 argv[0],但要使用 unsigned int * 有色眼镜。

标签: c pointers casting dereference


【解决方案1】:

注意:这个答案解释了代码在这种特殊情况下是如何工作的,更完整的答案请参阅@Lundin 帖子。


(unsigned int *)(argv[1])char* 转换为unsigned int*

*((unsigned int *)(argv[1])) 取消引用将char* 转换为unsigned int*

假设,argv[1] 指向 AAAA 字符串。它以41 41 41 41(十六进制)的形式存储在内存中。然后您将其解释为unsigned int,结果为ptr1 = 0x41414141

看下图:左查看器将内存解释为char*,右查看器将其解释为unsigned int*(不关心little-endian / big-endian的区别):

所以,没有段错误的原因

【讨论】:

  • 对不起,但这不是一个好的答案,因为您几乎没有提到代码中所有定义不明确的行为。这个答案的读者可能会认为 OP 的代码是非常好的代码,并且提到的教程不是废话。
  • So, there is no reason for segfault 不正确,有。您可以将任何内容转换为char*,但以相反的方式执行会调用未定义的行为。它不能保证工作,因为argv[1] 不一定正确对齐。它在这里工作只是因为 x86 支持非对齐访问,但大多数其他架构都禁止它
  • @Lundin,同意。等待 OP 取消选中接受按钮,以便我可以删除答案。
【解决方案2】:

这段代码既危险又糟糕。逐步解释代码的作用以及为什么不好:

  • argv 是一个指向字符 (char*[]) 的指针数组。或者,如果你愿意,一个指针 (char**) 指向这样一个字符指针数组中的第一项。

  • argv[0] 指向可执行文件的名称,argv[1] 是指向传递的第一个参数的指针。

  • (unsigned int *)(argv[1]) 将指向第一个参数 char* 的指针转换为指向 int 的指针 unsigned int*。这决不能保证是安全的转换。这里有两个主要错误:

    • 如果这个新的整数地址未对齐,访问它会引发未定义的行为,可能会导致程序崩溃。

    • 这种转换违反了严格的别名规则,该规则(简单地说)表明编译器可能假定char* 指向的内容永远不会通过其他随机指针类型访问。所以编译器可能会假设你的程序永远不会使用argv[i] 指向的内存。当您调用未定义的行为时,可能会发生任何奇怪的优化。

  • 假设特定编译器将指针转换的确定性行为指定为非标准扩展,那么它将尝试访问指向的字符串,就好像它是一个整数一样。例如,如果字符串是 "ABCD",那么生成的整数(假设为 32 位)将是 0x414243440x44434241。哪个适用取决于 CPU 字节序。这样的代码是不可移植的。

但是,仅仅访问argv 数组指向的内存当然不会造成任何影响。如果您无法读取此内存,那么您将无法使用 argv 参数。它们的确切存储方式取决于操作系统,但它必须位于允许进程访问的地址空间内。

因此,只要您在分配的内存中保持访问权限,程序肯定不会因此而崩溃或出现段错误。如果字符串 argv[1] 的长度只有 1 个字符,那么您可能会遇到 seg 错误。

【讨论】:

  • 建议:请立即停止观看该 Youtube 教程。发表评论并说这是一个糟糕的教程。随意发布此帖子的链接,以警告其他人不要观看。
  • 其实作者承认代码很恶心,但是上下文不同。这是视频的link
  • @user1720897 嗯...杜尔。该视频中的代码与您的问题中的代码不同。他将结果存储在unsigned int* 中,而您将结果存储在unsigned int 中。完全不同的程序(但同样糟糕)。该视频似乎是关于将数据黑客攻击到各种静态内存位置,并具有通常的车库黑客心态:“希望这有效”或“哇这次成功了”。
  • 是的。代码不同。我正在玩代码试图向自己解释代码,但我偶然发现了一些我不明白的东西。
  • @user1720897 将结果存储在指针中更有意义,这意味着他实际上是在尝试通过命令行参数将地址传递给他自己的程序,但是以一种混淆和糟糕的方式.
猜你喜欢
  • 2017-08-18
  • 1970-01-01
  • 2011-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-17
相关资源
最近更新 更多