C 语言的独特性

C 是一种高效、简洁的语言,但它也有一些编程人员必须了解的特性。为了应对这些特性,有时一个好的解决方案是将 C 与其他语言结合使用,以增强灵活性和功能,例如在 Emacs 中使用 Emacs-LISP 与 C 的结合。也有时,通过使用特殊结构来保证功能和安全,可能会牺牲速度并增加复杂性。然而,大多数 C 程序员通过实践,对这些特性没有太多困难,并且更倾向于使用一种与通用冯·诺依曼硬件架构紧密匹配的语言。

下面列出了一些 ANSI C 的特性(这些特性有时也是其优点),其中包括一些小问题和一些大问题:

数组和指针没有区分

最初的 C(大约在 1973 年)根本没有数组,现代实现中的数组实际上是通过指针运算访问的连续内存区域(注意:声明的数组不能像指针一样赋值),这避免了必须声明固定大小数组的需要。然而,这种特性可能会导致缓冲区溢出错误,特别是在不小心使用时。

数组不存储其长度

这是上述特性的一个结果。这意味着程序可能需要显式地进行边界检查,才能安全访问数组。除非函数传入一个固定大小的数组,否则函数无法知道所传递数组的长度:因此,函数必须显式地知道数组的长度,这可能通过将长度作为单独的变量或结构体传递给函数来实现。因此,大多数实现并没有提供自动数组边界检查,手动边界检查容易出错。

如果 C(或 C++)程序试图访问超出实际分配内存的数组元素,则会发生缓冲区溢出,通常会导致程序崩溃。缓冲区溢出漏洞也是常见的安全隐患。许多其他计算机语言提供了自动边界检查,因此它们几乎免疫于此类错误。

可变长度数组

可变长度数组(VLA)只能用于函数参数和自动变量。VLA 不能用于结构体内部(除非是结构体中的最后一项)。不能定义一个符合标准 Forth 字典定义(它有两个可变长度部分)的结构体,除非它是一个未区分的字符数组。

不广泛支持任意大小的内建二维或三维数组

从 C99 规范开始,C 引入了可变长度数组(VLA)的特性,但许多 C 编译器仍不支持此功能。在没有 VLA 的情况下,函数无法接受任意大小的二维或三维数组。特别是,无法定义一个函数来接受 int a[5][4][3];,然后在下一次调用时接受 int b[10][10][10];。因此,C 程序员通常使用其他数据类型来表示(数学)二维或三维数组,详细内容请参见 C 编程/常见做法#动态多维数组

没有正式的字符串数据类型

字符串是字符数组(没有任何抽象),并且继承了它们所有的限制(结构体在一定程度上可以提供抽象)。

类型安全较弱

C 语言并不是特别类型安全。内存管理函数作用于无类型指针,且没有内建的运行时类型强制检查,类型系统可以通过指针和类型转换绕过。此外,typedef 并不会创建新类型,只是创建了别名,因此它仅仅用于提高代码的可读性。不过,可以使用单成员结构体来强制类型安全。

没有垃圾回收

作为一种低级语言,C 只提供手动内存管理,这可能导致简单的内存泄漏问题未被检查出来。

局部变量在声明时没有初始化

局部(但非全局)变量必须手动初始化;在此之前,它们包含的是内存中已有的内容。这并不罕见,但 C 标准并未禁止访问未初始化的变量(这可能导致错误)。

函数指针语法笨拙

函数指针的语法形式为 [返回类型] [名称]([参数1类型])([参数2类型]),这使得它们有点难以使用。typedef 可以缓解这种繁琐的语法。例如,typedef int fn(int i);。详细信息请参见 C 编程/指针和数组#函数指针

没有反射

在 C 程序中,无法在运行时将字符串评估为源代码语句。

嵌套函数不是标准特性

然而,许多 C 编译器(包括 GNU C)支持嵌套函数。

没有正式的异常处理

一些标准函数返回特殊值,必须手动处理。例如,malloc() 失败时返回 NULL。举例来说,必须将 getchar() 的返回值存储在 int 类型的变量中(而不是 char 类型)以可靠地检测文件结束符——详见 EOF 陷阱。没有适当错误处理的程序在正常情况下可能运行良好,但在异常情况下可能崩溃或发生其他故障。POSIX 系统通常使用 signal() 处理某些类型的异常(详见 C 编程/错误处理#信号)。一些程序使用 setjmp()longjmp()goto 来手动处理某些类型的异常(详见 C 编程/控制#最后的一点:gotoC 编程/协程)。

没有匿名函数定义

参考资料

  1. Web Application Security Projects - Buffer Overflow
  2. Secure Programs HOWTO: Buffer Overflow
  3. Search Security - Buffer Overflows
  4. OWASP - Buffer Overflows
  5. Cyclone Language - Why Cyclone
  6. GNU Manual: "Extensions to the C Language: Nested Functions"
最后修改: 2025年01月12日 星期日 13:32