为什么此 C 代码片段不会导致分段错误?

Why does this C code snippet not result in a segmentation fault?

int main(int argc, char **argv) {
    unsigned int ptr1 = *((unsigned int *)(argv[1]));
    printf("ptr1 = 0x%x\n", ptr1);
    exit(0);
}

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

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

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

ptr1 = 0x41414141

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

注意:此答案解释了代码在这种特殊情况下的工作方式,如需更完整的答案,请参阅@Lundin post。


(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差异):

所以,没有理由出现段错误

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

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

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

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

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

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

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

但是,仅仅访问argv数组指向的内存当然不会产生任何影响。如果你不能读取这段内存,那么你就不可能使用 argv 参数。它们的确切存储方式取决于 OS,但它必须位于允许您的进程访问的地址 space 内。

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