跳转到内容

C 编程/C 的特点

来自维基教科书,开放的书籍,开放的世界

C 是一种高效、简约的语言,它有一些程序员必须注意的特殊之处。为了解决这些问题,有时一个好的解决方案是将另一种语言与 C 结合使用,以获得额外的灵活性和功能,例如 Emacs-LISP 和 C 的组合用于 Emacs。有时可以通过使用保证功能和安全的特殊结构来解决它们,但代价是速度变慢和复杂度增加。然而,大多数情况下,通过实践,C 程序员不会遇到这里提到的问题,并且更喜欢使用一种与通用冯·诺依曼硬件架构紧密匹配的语言。

以下是 ANSI C 中的一些特点(有时也是它的优势),一些小,一些大

数组和指针之间缺乏区别
最早的 C(大约 1973 年)根本没有数组;现代实现是内存中连续的区域,使用指针算术进行访问(注意:声明的数组不能像指针一样赋值),这避免了使用固定大小声明数组的必要性。但是,这种能力在使用不当的情况下会导致缓冲区溢出错误。
数组不存储它们的长度
上述特性的一个后果。这意味着程序可能需要在访问数组之前显式地执行边界检查。除非函数被传递一个固定大小的数组,否则它无法发现它被传递的数组的长度:因此,函数必须被传递长度,也许作为单独的变量传递给函数或在结构中传递。因此,大多数实现不提供自动数组边界检查,而手动边界检查容易出错。
如果 C(或 C++)程序试图访问超出实际分配内存的数组元素,则会发生缓冲区溢出,通常会使程序崩溃。缓冲区溢出错误也是常见的安全漏洞。许多其他计算机语言提供自动边界检查,因此它们几乎不受此类错误的影响。[1][2][3][4][5]
可变长度数组
VLA ‒ 可变长度数组 ‒ 只能用于函数参数和自动变量。VLA 不能在结构体内部使用(除非作为结构体的最后一个项目)。不可能定义一个对应于标准 Forth 字典定义(它有两个可变长度部分)的结构,除非作为 char 的未区分数组。
内置的 2D 或 3D 数组的大小不受限制
此功能已从 C99 规范开始添加,用于可变长度数组,尽管许多 C 编译器仍然不支持它。如果没有 VLA,函数无法接受任意大小的 2D 或 3D 数组。特别是,不可能定义一个接受 int a[5][4][3]; 在一次调用中,并在以后调用中接受 int b[10][10][10]; 。而不是使用内置的 2D 或 3D 数组数据类型,C 程序员使用其他数据类型来保存(数学)任意大小的 2D 或 3D 数组(多维数组) - 请参见 C 编程/常见做法#动态多维数组 了解详细信息。
没有正式的字符串数据类型
字符串是字符数组(缺乏任何抽象),并继承了它们的所有约束(结构可以在一定程度上提供抽象)。
类型安全性较弱
C 的类型安全性不是很高。内存管理函数在无类型指针上操作,没有内置的运行时类型强制,并且可以通过指针和强制转换来绕过类型系统。此外,typedef 不会创建新类型,而只是创建别名,因此它仅用于代码可读性。但是,可以使用单成员结构来强制类型安全性。
没有垃圾回收
作为一种旨在最小化开销的低级语言,C 仅提供手动内存管理,这可能导致简单的内存泄漏不受控制地继续。
局部变量在声明时未初始化
局部变量(但不是全局变量)必须手动初始化;在此之前,它们包含声明时内存中的任何内容。这并不罕见,但 C 标准并不禁止访问未初始化的变量(这是)。
笨拙的函数指针语法
函数指针采用 [返回值类型] [名称]([参数 1 类型])([参数 2 类型]) 的形式,使得它们难以使用。typedef 可以缓解这种繁重的语法。例如,typedef int fn(int i);。有关更多详细信息,请参见 C 编程/指针和数组#指向函数的指针
没有反射
C 程序在运行时无法将字符串评估为源 C 代码语句。
嵌套函数不是标准
但是,许多 C 编译器确实支持嵌套函数,包括 GNU C。[6]
没有正式的异常处理
一些标准函数返回特殊值,必须手动处理。例如,malloc() 在失败时返回 null。例如,必须将 getchar() 的返回值存储在 int 中(而不是预期的那样,存储在 char 中),以便可靠地检测文件结束 - 请参见 EOF 陷阱。不包含适当错误处理的程序在大多数情况下可能运行良好,但在出现异常情况时可能会崩溃或出现其他故障。POSIX 系统通常使用 signal() 来处理某些类型的异常。(有关详细信息,请参见 C 编程/错误处理#信号)。一些程序使用 setjmp()longjmp()goto 来手动处理某些类型的异常。(有关详细信息,请参见 C 编程/控制#最后一点:gotoC 编程/协程)。
没有匿名函数定义

参考文献

[编辑 | 编辑源代码]
华夏公益教科书