LLVM 项目博客

LLVM 项目新闻和来自战壕的详细信息

Google 的 C++:前方有龙

Google 拥有世界上最大的单体 C++ 代码库之一。我们每天有数千名工程师处理数百万行 C++ 代码。为了帮助整个系统保持运行,并使所有这些工程师保持快速高效,我们不得不构建一些独特的 C++ 工具,以 Clang C++ 编译器为中心。这些工具可以帮助工程师了解他们的代码,并在错误进入我们的生产系统之前阻止它们。

(在 Google 工程工具博客 上交叉发布)

当然,提高 Google 工程师的速度——他们的生产力——并不总是与传统意义上的速度相关联。它需要 Google 工程工作整体加速。仅仅让任何一个工具更快是不够的;整个流程必须从头到尾进行改进。

作为一个性能狂,我喜欢用熟悉的术语来思考这个问题。它类似于算法性能改进。当您减少工程师完成工作所需的总工作量,或从根本上改变工作所需的时间范围时,您会获得生产力的“算法”改进。然而,提高单一任务所需的时间往往会违背关于性能调优、80/20 规则以及过度优化陷阱的所有格言。

获得这些算法改进生产力的最佳方法之一是完全删除一组任务。让我们以对严重生产错误进行分类和调试的任务为例。如果您参与过大型软件项目,您可能已经见过在代码审查、测试和质量保证过程中被忽略的错误。当这些错误进入生产环境时,会对开发人员的生产力造成巨大的损失,因为工程师要应对停机、数据丢失和用户投诉。

如果我们可以构建一个工具来自动查找这些类型的错误呢?如果我们可以阻止它们永远不会使服务器宕机、到达用户数据或导致寻呼机发出警报呢?许多这些错误归结为简单的 C++ 编程错误。考虑以下代码片段

Response ProcessRequest(Widget foo, Whatsit bar, bool *charge_acct) {
// Do some fancy stuff...
if (/* Detect a subscription user */) {
charge_acct = false;
}
// Lots more fancy stuff...
}


您看到错误了吗?仔细的测试和代码审查会不断地捕获这些错误和其他错误,但不可避免地,其中一个错误会溜过去,因为代码看起来很正常。它明明白白地说它不应该在此时扣费。不幸的是,C++ 坚持认为 'false' 与 '0' 相同,'0' 可以是指针,也可以是布尔标志。此代码将指针设置为 NULL,并且从未触碰标志。

人类不擅长发现这种狡猾的错误,就像人类不擅长将 C++ 代码翻译成机器指令一样。我们有工具可以做到这一点,在这种情况下,首选的工具是编译器。任何编译器都不行,因为上面的代码只是一个错误示例,我们需要教会我们的编译器找出其他许多示例。我们还必须小心确保开发人员会根据这些工具提供的的信息采取行动。在 Google 的 C++ 代码库中,这意味着我们将每个编译器诊断信息都作为构建错误,即使是警告也是如此。我们不断需要增强我们的工具,根据新的模式在新的代码中找到新的错误,同时还要保持足够的精确度,以便立即中断构建,并对代码错误具有高度信心。

为了解决这些问题,我们在 Google 启动了一个项目,该项目正在与 LLVM 项目 合作开发 Clang C++ 编译器。我们可以快速在 Clang 中添加警告并自定义它们以发出关于危险和潜在错误构造的精确诊断信息。Clang 被设计为一个库集合,其明确的目标是支持各种工具和应用程序用途。这些库可以直接集成到 IDE 和命令行工具中,同时仍然构成编译器本身的核心。

我们已经研究 Clang 超过一年了,以便它能够理解和推断 Google 中的所有 C++ 代码。但是,构建捕获这些错误的工具和技术只是成功的一半;我们还必须让工程师使用它们。当 Google 的其他团队对生产错误做出响应时,我们的团队通常会开始努力启用可能捕获该错误的任何 Clang 诊断信息。在生产问题出现一周内,我们可以使用这些诊断信息扫描整个代码库,以修复任何潜在的错误。

最近,我们在 Google 的每个 C++ 构建中启用了 Clang C++ 编译器,以便为工程师提供准确且有用的警告和诊断信息。有关 Clang 如何帮助开发人员处理错误代码的一些示例,请参阅 LLVM 博客上的这篇博文。除此之外,一旦我们使用查找错误的诊断信息扫描了代码库,就可以为我们所有的工程师启用它,以便在他们提交代码之前捕获未来的错误。这些诊断信息会中断该软件部分的整个构建,以确保它们不会被忽略并立即得到处理。对于上面的代码示例,用户会收到一条错误消息

example1.cc:4:17: error: initialization of pointer of type 'bool *' from literal 'false' [-Werror,-Wbool-conversions]
charge_acct = false;
^


以下是我们发现的其他两类错误:

long kMaxDiskSpace = 10 << 30; // Ten gigs ought to be enough for anybody.

void SomeService() {
// Setup task using external resource...
while (/* Check if resource is available yet ... */) {
sleep(0.5); // Yield the CPU
}
}


现在会触发以下错误

example2.cc:12:25: error: shift result (10737418240) requires 35 bits to represent, but 'int' only has 32 bits [-Werror,-Wshift-overflow]
long kMaxDiskSpace = 10 << 30;
~~ ^ ~~
example2.cc:16:11: error: implicit conversion turns literal floating-point number into integer: 'double' to 'unsigned int' [-Werror,-Wliteral-conversion]
sleep(0.5);
~~~~~ ^~~


所有这些都代表我们代码中发现的真实错误,我们今天借助 Clang 正在捕获和修复这些错误。

Clang 及其诊断信息不会以任何方式消除对认真代码审查和彻底测试的需要。相反,它们补充了这些实践,相结合可以帮助减少我们代码中的错误数量。这是我们未来为工程师开发新诊断信息的平台。这是我们如何提高他们的生产力并加速 Google 的方式。

敬请关注有关我们如何将 Clang 推出给 Google 工程师、我们如何增强 Clang 以使其更适合我们的代码和开发人员需求,以及我们在该平台上构建的一些令人兴奋的工具的更多博文。