下面显示的指针赋值有什么区别:
int i = 5;
int *ptr = &i;
VS
int i = 5;
int *ptr;
ptr = &i;
VS
int i = 5;
int *ptr;
*ptr = &i;
我认为第1和第2是相同的但第3是不同的。所以我的问题是1,2和3之间的差异是什么?
答案 0 :(得分:2)
前两个语句做同样的事情,但有理由(我会进入)更喜欢第一种风格到第二种风格。第三个是未定义的行为,这非常糟糕。它也应该是一个类型错误。
如果你的编译器没有大惊小怪地接受这个代码,你就没有打开足够的警告进行编译。它应该注意到你正在取消引用一个未初始化的指针,并且你正在指定一个指向int
而没有强制转换的指针。在gcc或clang上,您可以添加标记-Wall -Wextra -Wpedantic -Wconversion
和-std=c11
或您要定位的任何内容。
在第三种情况下,陈述int i = 5;
与下一步的内容大多无关。声明int *ptr;
是一个未初始化的指针。然后,*ptr = &i;
取消引用未初始化的指针,如果你幸运的话会立即崩溃程序,然后在那里,让你找到问题。如果你运气不好,它会把某些内存的其他部分弄得一团糟,这会造成一个你无法重现的奇怪且不可预测的错误,并且不会暗示它来自哪里。
防御性编码的一个好习惯是将尽可能多的变量写为静态单一赋值。也就是说,只要有可能,您将变量声明并初始化为const
,并且永远不会再次修改它们。在这种情况下,那将是:
const int i = 5;
const int * const ptr = &i;
这也消除了一大类错误,我使用较新的变量值,我打算使用较旧的变量值,反之亦然。并且编译器无法为我捕获这些。如果i
和ptr
只有一个含义,那么当我重构时,不可能混淆它在任何给定时间具有的含义。至于效率,它是一种洗涤。如果我正在根据一个值进行计算,我要么会再次需要原始值,要么我不会。如果我这样做,覆盖它就是一个bug。如果不这样做,依赖性分析应该确定编译器不需要再保持值。
你不能总是这样做:一个非常常见的例子是循环计数器。在这些情况下,一个体面的习惯(虽然你可以找到我不虔诚地遵循我自己的建议的例子)是始终将所有内容初始化为0
,NULL
或其他一些明显无效的值。这样,如果你在真正初始化之前不小心使用了一个,那么至少结果是错误的和可重复的。所以:
int i = 5;
int *ptr = NULL;
*ptr = &i; /* FIXME: Undefined behavior! */
在您将要定位的大多数操作系统上,这至少会在发生逻辑错误的位置立即崩溃您的程序,从而使其易于查找。 (因为在过去,我的C和C ++答案引起了一两个人的注意,他们想要指出语言标准不能保证这个,我同意,它不可移植。但是我发现它在实践中很有用。)否则,如果您在调试器中单步执行,查找错误,那么变量在使用时未初始化是非常明显的,而如果它只是包含堆栈中的垃圾,可能不是。
另一方面,如果您的编译器可以检测到正在使用的未初始化变量,这种习惯可能会阻止它这样做。