C:为什么未分配的指针指向不可预测的内存而NOT指向NULL?

Ada*_*ent 46 c pointers

很久以前我曾经在C学校上学.我记得我真的讨厌C的东西:未分配的指针不指向NULL.

我问过包括教师在内的很多人为什么他们会将未分配指针默认行为指向NULL,因为它似乎更难以预测.

答案应该是表现,但我从未买过.我认为,如果C默认为NULL,那么编程历史中的许多错误都可以避免.

这里有一些C代码指出(双关语)我在说什么:

#include <stdio.h>

void main() {

  int * randomA;
  int * randomB;
  int * nullA = NULL;
  int * nullB = NULL;


  printf("randomA: %p, randomB: %p, nullA: %p, nullB: %p\n\n", 
     randomA, randomB, nullA, nullB);
}
Run Code Online (Sandbox Code Playgroud)

编译警告(很高兴看到C编译器比我在学校时更好)和输出:

randomA:0xb779eff4,randomB:0x804844b,nullA:(nil),nullB:(nil)

nin*_*alj 41

实际上,它取决于指针的存储.带有静态存储的指针使用空指针初始化.具有自动存储持续时间的指针未初始化.见ISO C 99 6.7.8.10:

如果未显式初始化具有自动存储持续时间的对象,则其值不确定.如果未显式初始化具有静态存储持续时间的对象,则:

  • 如果它有指针类型,则将其初始化为空指针;
  • 如果它有算术类型,则初始化为(正或无符号)零;
  • 如果是聚合,则根据这些规则初始化(递归)每个成员;
  • 如果它是一个联合,则根据这些规则初始化(递归)第一个命名成员.

是的,出于性能原因,没有初始化具有自动存储持续时间的对象.想象一下,在每次调用日志函数时初始化一个4K数组(我在一个项目中看到的东西,谢天谢地,C让我避免初始化,从而带来不错的性能提升).

  • 是的,性能对该项目非常重要.除此之外,日志函数只有99.99%的时间根据共享内存中的某些标志检查其参数,并看到日志记录已被禁用并返回.当我发现初始化位于cachegrind配置文件的前5位之一时,想象一下我的表达式. (5认同)

det*_*tly 26

因为在C中,声明和初始化是故意不同的步骤.它们是故意不同的,因为这就是C的设计方式.

当你在函数中说这个时:

void demo(void)
{
    int *param;
    ...
}
Run Code Online (Sandbox Code Playgroud)

你说,"亲爱的C编译器,当你为这个函数创建堆栈帧时,请记住保留sizeof(int*)用于存储指针的字节." 编译器不会询问那里会发生什么 - 它假设你很快会告诉它.如果你不这样做,也许对你有更好的语言;)

也许生成一些安全的堆栈清除代码并不困难.但它必须在每个函数调用上被调用,我怀疑许多C开发人员会在他们自己完全填充它时才会喜欢它.顺便提一下,如果你允许灵活使用堆栈,那么你可以为性能做很多事情.例如,编译器可以进行优化......

如果您function1调用另一个function2并存储其返回值,或者可能存在传入的一些参数function2未在内部更改function2...我们不必创建额外的空间,是吗?只需使用堆栈的相同部分!请注意,这与每次使用前初始化堆栈的概念直接冲突.

但是从更广泛的意义上说(而且在我看来,更重要的是)它与C的理念是一致的,即不是做得非常绝对必要.无论您是在使用PDP11,PIC32MX(我用它)还是Cray XT3,这都适用.这正是为什么人们会选择用C来代替其他语言的.

  • 如果我想编写一个程序,没有痕迹mallocfree,我不就得了!没有内存管理强加给我!
  • 如果我想对数据联合进行bit-pack和type-pun,我可以!(当然,只要我阅读我的实施关于标准依从性的说明.)
  • 如果我确切知道我正在使用我的堆栈帧做什么,编译器不必为我做任何其他事情!

总之,当你要求C编译器跳转时,它不会问多高.结果代码可能甚至不会再次降低.

由于大多数选择用C开发的人都喜欢这种方式,因此它具有足够的惯性而不会改变.你的方式可能不是一个天生就是坏主意,它并没有被许多其他C开发人员真正要求.

  • @Adam Gent:批评一种语言,理由是它不是你喜欢的那种语言是徒劳的,不是吗?C大量用于嵌入式编程的原因是它很有效.如果您销售数以千万计的系统,那么让工程师保证代码安全,而不是在更快的CPU或更大的程序存储上花费额外的美元便宜得多. (3认同)
  • @Adam Gent - 我希望在我的高精度科学仪器中严格控制时间和记忆:)这是安全的,因为我随时都知道我的代码正在做什么 - 我告诉它要做什么.您对安全的定义可能有所不同.(另外,找到一个用于PIC32MX系列或dsPIC24的Haskell编译器.) (2认同)
  • @detly touche .. touche ...你的观点被采纳了,我并不想冒犯.但是,如果这是真的,为什么不在程序集编程呢?这是2010年的权利,芯片变得相当便宜?我们什么时候可以停止使用C语言进行编程,或者您认为C是否优越?这些天我们不是要转向更加并发的架构了吗?看来C在这方面有点弱. (2认同)
  • @NoMoreZealots和@detly我会同意C确实很好地映射****真正有用的东西.我很高兴我学习了这门语言,因为它帮助我理解了我在学校时的CPU架构. (2认同)
  • @Adam Gent:有趣的是,除了可以轻松抽象的IO问题之外,CPU绑定应用程序还需要并发编程.将C应用于据称在更高级语言中更容易使用的区域,大多数时候*消除了对并发编程*的需要,因为与其他语言的实现相比,性能极高.如果这仍然不够,那么无论如何,您都希望将C与并发实现一起使用.切换到C:50倍性能.切换到并发:次线性核心计数性能提高. (2认同)

Dav*_*kes 14

这是为了表现.

C最初是在PDP 11的时代开发的,其中60k是常见的最大内存量,许多内存将少得多.在这种环境下,不必要的任务特别昂贵

目前有许多嵌入式设备使用C,其中60k的内存看起来无限,PIC 12F675有1k的内存.

  • @Adam:价值在那之前.它只是重用特定的内存位置. (12认同)
  • 运行时没有分配任何东西,只是重用那里发生的事情. (4认同)

sla*_*ppy 8

这是因为当您声明一个指针时,您的C编译器将保留必要的空间来放置它.所以当你运行你的程序时,这个空间可能已经有了一个值,可能是因为在这部分内存中分配了先前的数据.

C编译器可以为该指针赋值,但在大多数情况下这会浪费时间,因为除了在代码的某些部分自己分配自定义值之外.

这就是为什么好的编译器在你不初始化你的变量时会发出警告; 所以我不认为因为这种行为会有这么多错误.你只需要阅读警告.


caf*_*caf 7

指针在这方面并不特别; 如果您使用未初始化的其他类型的变量具有完全相同的问题:

int a;
double b;

printf("%d, %f\n", a, b);
Run Code Online (Sandbox Code Playgroud)

原因很简单:要求运行时将未初始化的值设置为已知值会增加每个函数调用的开销.单个值的开销可能不大,但请考虑是否有大量指针:

int *a[20000];
Run Code Online (Sandbox Code Playgroud)