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])
这看起来像传递给程序的第一个参数被转换为unsigned int。因此,如果参数是'AAAA',那么'0x41414141'现在是指向内存中某个位置的指针。
然后当我们执行此操作时*((unsigned int *)(argv[1]))
,我们不是要引用地址0x41414141
指向的值吗?据我所知,该过程可能无法访问此地址。那怎么没有发生分段错误呢?该程序的输出是 -
ptr1 = 0x41414141
我已经在Linux上使用gcc编译了这个程序。
答案 0 :(得分:5)
注意:此答案解释了代码在此特定情况下的工作原理,有关更完整的答案,请参阅@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差异):
所以,没有理由让segfault
答案 1 :(得分:5)
这段代码既危险又不好。逐步说明代码的作用以及为什么不好:
argv
是指向字符(char*[]
)的指针数组。或者,如果你愿意的话,指针(char**
)到这样一个指向角色的指针数组中的第一个项目。
argv[0]
指向可执行文件的名称,argv[1]
是指向传递的第一个参数的指针。
(unsigned int *)(argv[1])
将指向第一个参数的指针char*
转换为指向int unsigned int*
的指针。这绝不保证是安全的转换。这里有两个主要的错误:
如果这个新的整数地址未对齐,访问它将调用未定义的行为,可能是程序崩溃。
转换违反了严格别名规则,该规则(简单地说)表明编译器可能认为char*
指向的内容永远不会通过其他随机指针类型访问。因此编译器可能会认为argv[i]
指向的内存从未被您的程序使用过。当您调用未定义的行为时,可能会发生任何奇怪的优化。
鉴于特定编译器将指针转换的确定性行为指定为非标准扩展,那么它将尝试访问指向的字符串,就好像它是一个整数一样。如果字符串是例如"ABCD"
,则结果整数(假设为32位)可以是0x41424344
或0x44434241
。哪个适用取决于CPU的endianess。这样的代码是不可移植的。
但是,只访问argv
数组指向的内存当然不会产生任何影响。如果您无法读取此内存,则无法使用argv参数。它们的存储方式究竟是依赖于操作系统的,但它必须是允许进程访问的内部地址空间。
因此,只要您在分配的内存中保持访问权限,程序肯定不会崩溃或出错。如果字符串argv[1]
只有1个字符长,那么您可能会遇到段错误。