电子产业一站式赋能平台

PCB联盟网

搜索
查看: 21|回复: 0
收起左侧

C 语言的困境:为什么这些难题至今没有得到解决?

[复制链接]

524

主题

524

帖子

5467

积分

四级会员

Rank: 4

积分
5467
发表于 昨天 09:00 | 显示全部楼层 |阅读模式
本文经授权转自公众号CSDN(ID:CSDNnews)
作者 | Walter Bright    翻译工具 | ChatGPT,责编 | 苏宓
编程语言和编译器技术的发展,始终是推动软件工程进步的核心动力。然而,即便是广泛使用的标准 C 语言,至今仍存在一些令人困惑的问题亟待解决。在这篇文章中,我们将以编译器专家、D 编程语言的创造者和首位实现者 Walter Bright 的观点为切入点,探讨现代编译器技术如何弥补传统 C 语言的不足。
尽管标准 C 语言在不断演进(当前版本为 C23),仍有一些令人费解的缺陷尚未修复。而此前 Dlang 社区在其 D 编程语言编译器中嵌入了一个全新的 C 编译器(称为 ImportC,https://dlang.org/spec/importc.html),以支持 C 代码编译。这一全新构建的编译器利用了现代编译技术,解决了一些传统 C 的问题。
问题来了:为什么标准 C 语言没有修复这些缺陷?譬如:
  • 常量表达式的评估(Evaluating Constant Expressions)
  • 编译时单元测试(Compile Time Unit Tests)
  • 前向引用声明的问题(Forward Referencing of Declarations)
  • 导入声明问题(Importing Declarations)


    1、常量表达式的评估
    来看以下 C 代码示例:
  • int sum(int a, int b) { return a + b; }enum E { A = 3, B = 4, C = sum(5, 6) };使用 gcc 编译时会报错:
  • gcc -c test.ctest.c:3:20: error: enumerator value for C is not an integer constantenum E { A = 3, B, C = sum(5, 6) };^通俗来说,虽然 C 语言能通过常量折叠(Constant Folding)在编译时计算出一个简单的表达式,但它无法在编译时执行函数调用。然而,ImportC 可以做到这一点。
    建议改进:
    在 C 语言语法中,凡是可以使用常量表达式的地方,编译器都应该能够在编译时执行函数,只要这些函数不涉及诸如 I/O 操作、访问可变的全局变量、进行系统调用等行为。


    2、编译时单元测试
    一旦 C 编译器具备了编译时函数求值(CTFE)的能力,许多新功能就变得可能。
    在 C 代码中,很少见到单元测试,原因很简单:单元测试需要在构建系统中单独设置目标,并以独立的可执行文件形式运行。这种额外的复杂性导致开发者往往忽略它。也许你是一位每天坚持晨跑的人,同时认真设置单元测试的场景,但大多数开发者并非如此。
    例如:
  • int sum(int a, int b) { return a + b; }_Static_assert(sum(3, 4) == 7, "test #1");使用 gcc 编译时会报错:
  • gcc -c test.ctest.c:3:16: error: expression in static assertion is not constant_Static_assert(sum(3, 4) == 7, "test #1");^然而,ImportC 可以成功编译这段代码。
    潜在改进:
    通过允许在编译时运行函数,C 语言可以轻松实现单元测试功能。无需额外的构建步骤或独立的测试可执行文件,每次代码编译时单元测试都会自动运行。例如,在 ImportC 的测试框架中,这种编译时测试得到了广泛应用。


    3、前向引用声明的问题
    在 C 语言中,函数的前向引用是一种常见的限制。以下代码展示了这个问题:
  • int floo(int a, char *s) { return dex(s, a); }char dex(char *s, int i) { return s; }使用 gcc 编译时会报错:
  • gcc -c test.ctest.c:4:6: error: conflicting types for dexchar dex(char *s, int i) { return s; }^test.c:2:35: note: previous implicit declaration of dex was hereint floo(int a, char *s) { return dex(s, a); }错误的原因是:编译器在解析 floo 时对 dex 的类型做了一个隐式假设(通常假设返回值为 int),但实际定义的类型与此假设不符。如果将 floo 和 dex 的顺序颠倒,这段代码就可以正常编译。这说明 C 编译器只能识别代码中“词法顺序上位于之前的声明”,而前向引用(即在定义之前调用函数)是不被允许的。
    为什么这是一个问题?
    是因为为了支持前向引用,每个函数都需要一个显式的声明。例如:
  • char dex(char *s, int i); // 声明int floo(int a, char *s) { return dex(s, a); }char dex(char *s, int i) { return s; } // 定义这种声明和定义的分离增加了无意义的工作量。
    其次,这种限制迫使开发者调整代码顺序,比如将叶子函数(底层函数)放在最前面,而全局接口函数放在最后。这种代码组织方式类似于从报纸的底部开始阅读,非常不符合直觉。
    ImportC 允许在任何顺序下编译全局声明,也就是说,开发者不必担心函数定义的前后顺序问题。

    4、导入声明问题
    在传统 C 编程中,处理多个文件模块时,通常需要单独创建 .h 头文件以声明模块接口。以下示例展示了常见的文件结构:
  • // floo.c#include "dex.h"int floo(int a, char *s) { return dex(s, a); }
  • // dex.hchar dex(char *s, int i);
  • // dex.c#include "dex.h"char dex(char *s, int i) { return s; }问题点:
    1.        繁琐:每个外部模块都需要一个单独的 .h 文件,这增加了开发者的负担。
    2.        易错:如果 .h 文件的声明和 .c 文件的定义不完全匹配,会导致各种难以排查的错误。
    在 ImportC 中,直接导入模块的源文件即可,无需额外的头文件。例如:
  • // floo.c__import dex;int floo(int a, char *s) { return dexx(s, a); }
  • // dex.cchar dexx(char *s, int i) { return s; }通过这种方式,开发者完全不需要编写 .h 文件,避免了繁琐的声明工作,同时减少了潜在的错误风险。
    相关阅读:
  • importC 文档:https://dlang.org/spec/importc.html
  • D 语言文档:https://dlang.org/
    本文转自公众号“CSDN”,ID:CSDNnews

    wfxbx5gkpuf6401264012.gif

    wfxbx5gkpuf6401264012.gif

    推荐阅读  点击标题可跳转1、C++训练营,来了!
    2、HarmonyOS 学习资料分享(无套路免费分享)
    我组建了一些社群一起交流,群里有大牛也有小白,如果你有意可以一起进群交流。

    caqkmggb5rn6401264112.png

    caqkmggb5rn6401264112.png

    欢迎你添加我的微信,我拉你进技术交流群。此外,我也会经常在微信上分享一些计算机学习经验以及工作体验,还有一些内推机会。

    3ms2542uigr6401264212.png

    3ms2542uigr6401264212.png

    加个微信,打开另一扇窗
    感谢你的分享,点赞,在看三  

    sj4yojcmvue6401264312.gif

    sj4yojcmvue6401264312.gif

  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表