我们使用的工具

“只有业余爱好者才会受到工具的限制”是一种古老的智慧,世界上到处都是艺术和建筑,这很好地证明了这一点。

但是,尽管塞戈维亚的渡槽令人惊叹,但工具是它看起来远不及悉尼歌剧院的原因。

混凝土自古以来就为人所知,但钢筋混凝土和大量的应力分布数值计算,是将混凝土用作石头之间的填充材料,还是用作抗重力的弯曲但绝对安全的承重墙。

我编写Varnish的工具是C语言,在许多方面,它在所有计算机编程语言中是独一无二的,因为它没有野心。

C语言是作为一种可移植的汇编语言发明的,它不做对象和垃圾回收,它做数字和指针,就像你的CPU一样。

与当时和现在一样对新编程语言的雄心壮志相比,这几乎是荒谬而不雄心勃勃的。其他人试图使他们的编程语言被证明是正确的,或者对多道程序设计是安全的,并且相当努力地使用自然语言作为编程语言。

但C语言是用来编写程序的,而不是用来研究计算机科学的,而这正是它有用和流行的原因。

不幸的是,C多年来一直不受欢迎,而这种爆发的原因是我刚刚浏览了ISO-C标准化工作组14的最新草案。

我不会说这足以让成年男人哭泣,但这肯定足以让我生气。

让我给你举一个他们愚蠢透顶的例子:

定义C语言的那本书有一个保留标识符列表,所有这些都是小写的单词。Unix库定义了许多函数,所有这些函数都是小写单词。

在编译时,汇编器看到所有这些单词都带有下划线前缀,这使得汇编语言和C代码很容易混合。

另一方面,C预处理器的所有宏都是大写的,这使得它们很容易识别。

这意味着如果你在你的识别符中混合了大小写,你就是安全的:这不会与任何东西冲突。

首先,ISO-C标准让人们对前导下划线感到困惑,我将让您猜测当前文本的实际含义:

所有以下划线和大写字母或其他下划线开头的标识符始终保留以供任何使用。

请随意猜测,在草稿的第200页上有更多这样的内容。

接下来,他们打破了大小写规则,添加了大小写混合的特殊关键字,可能是因为他们认为这样看起来更好:

_Atomic, _Bool, _Noreturn &c

然后,想必有人指出这看起来很难看:

void _Noreturn foo(int bar);

因此,他们提供了一个名为<stdnoreur.h>的#Include文件,因此您可以编写::

#include <nostdreturn.h>
void noreturn foo(int bar);

根据标准的<nostdretur.h>文件应包含如下内容::

#define noreturn _Noreturn

你哭了还是笑了?你应该害怕。

新草案带来的另一件事是一个全新的线程API,它与已经使用了大约20年的POSIX‘p线程’API不兼容。

如果他们改进了p线程的缺点,我会为他们加油的,因为p线程中有一些非常恼人的错误。

但他们并没有,事实上,据我所知,C1X草案的线程在所有相关方面都比20岁的老版本更差。

例如,p线程和c1x线程都不提供“断言我持有这个互斥锁”功能。我假设,如果没有它,或者不浪费大量时间调试愚蠢的错误,您就不可能成功地开发真实的线程化程序和API。

如果您查看使用p线程的Varnish源代码,您将看到我在自己的小数据结构中包装了p线程互斥锁,以便能够执行这些断言,并获得有关锁争用的一些有用统计信息。

在定时休眠中,c1x完全没有改进p线程的另一个例子,您说“给我这个锁,但如果花费的时间超过X个时间就放弃”。

P线程和c1x线程实现这一点的方法是指定您想要睡眠到的UTC挂钟时间。

唯一的问题是,当在计算机上实现时,UTC挂钟时间不是连续的,它甚至可能不是单调增加的,因为NTPD或其他时间同步设施可能会使时钟倒退,特别是在引导后的第一分钟。

如果说“在16:00Z之前给我这个锁”的做法很普遍,我可以看到这一点,但实际上我从来没有在任何源代码中看到过这一点。我所看到的是一般形状的包装纸:

int
get_lock_timed(lock, timeout)
{
        while (timeout > 0) {
                t0 = time();
                i = get_lock_before(lock, t + timeout));
                if (i == WASLOCKED)
                        return (i);
                t1 = time();
                timeout -= (t1 - t0);
        }
        return (TIMEDOUT);
}

因为如果您请求调用,实际上并不能保证它在16:00Z返回,您只得到了它不会在此之后返回的承诺,所以您必须将调用包装在循环中。

无论是谁定义了SELECT(2)和Poll(2)系统调用,他都比POSIX和ISO-C组更清楚:他们指定了调用的最长持续时间,因为这样就不重要了,重要的是发生了多长时间。

哦,还有为新线程设置堆栈大小吗?这显然是“太危险了”,所以c1x API中没有这样做的参数,这显然是对p线程的一种倒退。

但你猜怎么着:线堆就像T恤衫:没有“一刀切”的。

我不知道他们感觉到的“危险”是什么,我最好的猜测是担心这会让API变得有用?

这种单一的愚蠢将单枪匹马地将c1x线程API推向无用的境地。

现在,不要误会我的意思:有很多方法可以改进C语言,这将是有意义的:位图、定义的结构打包(想想:通信协议包)、大/小端变量(数据共享)、合理处理链表等。

尽管很难看,但通过提供一种合理的插件机制,即使是printf()/scanf()格式的字符串也可以得到改进,编译器可以理解该机制并使用它来发出警告。

见鬼,即使是一个简单的基本对象工具也是很好的补充,因为C++已经成为这种巨大的臃肿的怪物语言。

但所有这些显然都不如<stdnoreur.h>和一个新的、残缺的、因此毫无用处的线程API那么重要。

C语言的巧妙之处在于,即使是ISO-C工作组也不能阻止您使用宏和其他技巧来实现所有这些事情,这也是使其如此流行的一个特性。

但最好将它们放在语言中,这样编译器就可以发出合理的警告,程序员就不必编写像::这样的怪物了。

#define VTAILQ_INSERT_BEFORE(listelm, elm, field) do {              \
    (elm)->field.vtqe_prev = (listelm)->field.vtqe_prev;            \
    VTAILQ_NEXT((elm), field) = (listelm);                          \
    *(listelm)->field.vtqe_prev = (elm);                            \
    (listelm)->field.vtqe_prev = &VTAILQ_NEXT((elm), field);        \
} while (0)

若要将元素放在链接列表上,请执行以下操作。

我可以这样继续下去,但对你我来说很快就会变得无聊,因为目前的c1x草稿有701页,而且它没有一个解释实例来说明如何在实践中使用任何措辞。

将它与C编程语言进行比较,这本书长达274页,除了定义C语言之外,还通过深思熟虑的例子教人们如何编程。

在我看来,ISO WG14正在摧毁我使用和热爱的C语言。

保尔-亨宁,2011-12-20