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

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

我询问了许多人,包括老师,为什么在这个世界上,他们会让未分配的指针默认行为不指向NULL,因为这似乎更危险,因为它是不可预知的。

答案应该是performance,但我从来没有买过。 如果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); } 

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

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

实际上,这取决于指针的存储。 静态存储指针初始化为空指针。 具有自动存储持续时间的指针不会被初始化。 参见ISO C 99 6.7.8.10:

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

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

是的,由于性能原因,具有自动存储持续时间的对象不会被初始化。 试想一下,在每次调用日志函数的时候初始化一个4K数组(我在一个项目上看到的东西,幸好C让我避免了初始化,从而提高了性能)。

因为在C中,声明和初始化是故意不同的步骤 。 他们故意不同,因为C就是这样devise的。

当你在一个函数里面这样说的时候:

 void demo(void) { int *param; ... } 

你说的是,“我亲爱的C编译器,当你为这个函数创build栈帧时,请记得保存sizeof(int*)个字节来存储一个指针。 编译器不会问那里有什么 – 它假定你很快就会告诉它。 如果你不这样做,也许对你来说有更好的语言;)

也许这不会恶意地产生一些安全的堆栈清除代码。 但是在每个函数调用中都必须调用它,而且我怀疑许多C开发人员会在他们自己填充它时感兴趣。 顺便说一句,如果您可以灵活地使用堆栈,则可以为性能做很多事情。 例如,编译器可以进行优化…

如果你的function1调用另一个function2并存储它的返回值,或者有一些参数传入function2 ,在function2中没有改变…我们不需要创build额外的空间,是吗? 只需使用两个堆栈的相同部分! 请注意,这与每次使用前初始化堆栈的概念有直接冲突。

但从更广泛的意义上说,(更重要的是,在我看来)这与C的哲学并不是绝对必要的。 无论您是在PDP11,PIC32MX(我使用它)还是Cray XT3上,这都适用。 这正是为什么人们可能会select使用C而不是其他语言。

  • 如果我想写一个没有mallocfree痕迹的程序,我不需要! 没有内存pipe理强加给我!
  • 如果我想打包并打字数据联合,我可以! (当然,只要我阅读我的实现关于标准遵从性的注释。)
  • 如果我确切地知道我在做什么,我的堆栈框架,编译器不需要为我做任何事情!

总之,当你要求C编译器跳转时,它不会问有多高。 生成的代码可能不会再回来了。

由于大多数人select以这种方式发展C,它有足够的惯性不能改变。 你的方式可能不是一个天生不好的想法,它只是没有被许多其他的C开发人员所要求。

这是为了performance。

C最早是在PDP 11的时候开发的,其中60k是最常见的内存,许多将会less得多。 这种环境中不必要的任务将特别昂贵

现在有许多使用C的embedded式设备,其中60K的内存似乎是无限的,PIC 12F675有1K的内存。

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

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

这就是为什么当你不初始化你的variables时,好的编译器会给出警告。 所以我不认为有这么多的错误,因为这种行为。 你只需要阅读警告。

指针在这方面并不特别; 其他types的variables具有完全相同的问题,如果您使用它们未初始化:

 int a; double b; printf("%d, %f\n", a, b); 

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

 int *a[20000]; 

当你在函数的开头声明一个(指针)variables时,编译器会做两件事情之一:将一个寄存器作为variables使用,或者为它分配栈空间。 对于大多数处理器来说,为堆栈中的所有局部variables分配内存是用一条指令完成的; 它会计算出所有本地variables需要多less内存,然后将堆栈指针拉下来(或者在某些处理器上)。 无论在那个时候已经存在的内容都不会改变,除非你明确地改变它。

指针不是“设置”为“随机”值。 在分配之前,堆栈指针(SP)下面的堆栈内存包含以前使用的内存:

  . . SP ---> 45 ff 04 f9 44 23 01 40 . . . 

在为本地指针分配内存后,唯一改变的就是堆栈指针:

  . . 45 ff | 04 | allocated memory for pointer. f9 | SP ---> 44 | 23 01 40 . . . 

这允许编译器在一个指令中分配所有本地variables,这个指令把栈指针向下移动(并且通过移动栈指针返回一个指令释放它们),但是强制你自己初始化它们,如果你需要的话去做。

在C99中,你可以混合使用代码和声明,所以你可以在代码中推迟你的声明,直到你能够初始化它。 这将允许您避免必须将其设置为NULL。

首先,强制初始化不会修复错误。 它掩盖了他们。 使用不具有有效值的variables(以及因应用程序而异)是一个错误。

其次,你可以经常做自己的初始化。 而不是int *p; ,写入int *p = NULL; 或者int *p = 0; 。 使用calloc() (初始化内存为零)而不是malloc() (不)。 (不,所有的零位不一定意味着NULL指针或浮点值为零。是的,它在大多数现代实现中都有。

第三,C(和C ++)的哲学就是让你有办法快速做一些事情。 假设你可以select用语言来实现一种安全的方式去做某件事,一种快速的方式去做某件事。 通过添加更多的代码,你无法以更快的速度获得安全的方式,但是通过这样做可以使安全性更高。 而且,通过确保操作安全无需额外检查,您有时可以快速安全地进行操作 – 当然假设您有快速的select。

C最初是为编写操作系统和相关代码而devise的,操作系统的某些部分必须尽可能快。 这在C中是可能的,但在更安全的语言中是这样。 而且,当最大的计算机比我口袋里的电话(我正在升级,因为感觉老而慢)中的电话不那么强大时,C就被开发出来了。 在经常使用的代码中保存几个机器周期可能会有明显的结果。

所以,总结一下ninjalj解释的,如果你稍微改变一下你的示例程序,那么你的指针就会初始化为NULL:

 #include <stdio.h> // Change the "storage" of the pointer-variables from "stack" to "bss" int * randomA; int * randomB; void main() { int * nullA = NULL; int * nullB = NULL; printf("randomA: %p, randomB: %p, nullA: %p, nullB: %p\n\n", randomA, randomB, nullA, nullB); } 

在我的机器上打印

randomA:00000000,randomB:00000000,nullA:00000000,nullB:00000000

我认为它来自以下内容:没有理由为什么回忆应该包含(当通电)特定的值(0,NULL或其他)。 因此,如果以前没有专门编写,内存位置可以包含任何值,从您的angular度来看,无论如何都是随机的(但是这个位置以前可能已经被其他软件使用过了,因此包含一个对于那个应用程序,例如一个计数器,但是从“你的”angular度来看,只是一个随机数)。 要将其初始化为特定值,至less需要一条指令; 但是有些情况下你并不需要这个初始化,例如v = malloc(x)将分配给va的有效地址或NULL,不pipev的初始内容如何,​​所以初始化它可能被认为是浪费时间,和一个语言(如C)可以select不先验的 。 当然,现在这个主要是微不足道的,还有一些语言的未初始化variables具有默认值(当指针为null时,数值为0 / 0.0 …等等;懒惰初始化当然不会太昂贵的初始化一百万个元素的数组,因为只有在赋值之前被访问,它们才被初始化为真实)。

除了embedded式系统之外,当机器启动时,与随机存储器内容有关的想法是虚假的。 任何具有虚拟内存和多进程/多用户操作系统的机器在将内存交给进程之前都会初始化内存(通常为0)。 不这样做将是一个重大的安全漏洞。 自动存储variables中的“随机”值来自之前由同一进程使用的堆栈。 同样,由malloc / new / etc返回的内存中的“随机”值。 来自以前的分配(随后被释放)在同一过程中。

因为它指向NULL,它将不得不分配NULL(即使它是自动和透明地完成)。

所以,要回答你的问题,指针既不能被分配也不能被分配的原因是因为指针不能同时被分配和分配。