编程与软件工程blog.robertelder.org
C 预处理器那些骇人听闻的怪事
Scandalous Weird Old Things About The C Preprocessor (2015)
2016 — 2020,它一次次回到首页
黑点是上首页(≥ 50 分)的那次投稿,空心点是没冲上去的投稿,点越大分数越高。
导读
这篇文章列举了 C 预处理器中七个古怪的历史遗留行为,例如指令对行首位置的敏感性、宏展开的怪异规则,以及 C99 标准中仅用 19 页描述预处理器等事实。文章指出预处理器本质上是一个独立的文本处理语言,而非 C 语言本身的组成部分。
为什么经久不衰:C 预处理器至今仍被几乎所有 C/C++ 项目使用,理解其怪异行为有助于避免实际开发中的坑,这篇梳理历经多年仍被反复推荐。
HN 评论精选
标准只用 19 页描述预处理器是刻意为之,警告人们不要滥用它做复杂元编程
C99 标准约 500 页,但只有 19 页描述预处理器——这不是疏忽,而是一种暗示:你不该用预处理器做太多事。用预处理器玩复杂花活的人,通常是在给自己埋雷,尤其是移植到不同操作系统或编译器时。如果你打算用预处理器做复杂的元编程,应该退一步重新考虑方案,并想想你离开后谁来维护这些代码。
jandrese查看英文原文
> The C99 standard is about 500 pages, but only 19 of them are dedicated to describing how the C preprocessor should work IMHO This is not a mistake. This is a subtle indication about how much you should be doing with the C preprocessor. People who try to do crazy clever tricks with the preprocessor are usually just creating a huge problem for themselves down the road, especially when porting over to a different OS or compiler. If you're considering doing complex metaprogramming with the preprocessor you need to step back and reconsider your approach and ask yourself who is going to maintain it once you've moved on.
纠正文章错误:对换行敏感不等于上下文相关文法,作者混淆了解析树与宏展开效果
第 2 点不正确。对换行敏感并不意味着语法是上下文相关的,只是要把换行当作 token 而非可忽略的空白(C11 标准给出的正是这样的上下文无关文法)。关于 token 拼接的说法也一样,那些例子的解析树都是静态的。作者似乎把预处理器的解析树和它对文本的效果混为一谈——这就像说 Lisp 是上下文相关语言,因为阶乘函数对不同输入产生不同结果一样。
colanderman查看英文原文
#2 is incorrect. Being sensitive to line breaks does not make a grammar context-sensitive. It just means you have to treat line breaks as tokens rather than ignorable whitespace (which is exactly what the context-free grammar given in the C11 standard does). Same with the bit about concatenating tokens. Every single one of those examples has a static parse tree, which, for the C preprocessor, is a sequence of tokens and directives. The author seems to be confusing the preprocessor's parse tree with the effect it has on the underlying text. (Yes, the output of the preprocessor is dependent on what you define, but that has nothing to do with the grammar. What the author claims is like saying a Lisp is context-sensitive because the factorial function produces a different values for different inputs!) Now, if you could do this: #define foobar define #foobar x 123 x and get "123", that would be a context-sensitive grammar. But that is NOT a thing you can do!
预处理器应被视为独立的文本处理语言,而非 C 的一部分,其怪异之处由此而来
说实话我对这个清单并不感冒,没有 surprises。我同意预处理器是历史遗留物,经不起时间考验,但文中提到的许多所谓缺陷源于一个误导性观念:把预处理器当作 C 语言本身的一部分。它更适合被看作一门独立语言(传统上由完全独立的程序实现),是一个为做 C 本身不能(或不该)做的事而提供的便捷文本处理语言。
breadbox查看英文原文
I hate to say it, but I was rather unimpressed by this list, and nothing in it surprised me. While I certainly agree that the C preprocessor is a relic, and has not weathered the test of time well, I would suggest that a number of the supposed infelicities mentioned in this article stem from the misleading idea that the preprocessor is an integral part of the C language proper, when it is better thought of as its own language (and one that was traditionally done by a completely separate program). The preprocessor does things differently than the rest of C, because it's not C. It is a text-processing language of convenience, provided specifically for doing things that C itself cannot (or should not) do.
预处理器是因实现容易而存在的历史产物,其文本替换特性甚至可被用于隐藏后门
C 预处理器是一种糟糕的元编程方式,之所以存在只是因为作为独立的一遍处理实现起来相对容易,很少有其他语言这样做。精通预处理器对写混淆 C 代码至关重要,例如 lucky7coin 后门事件:代码通过宏拼接把看似无害的条件判断展开成 popen 调用,隐藏了恶意行为。
pjc50查看英文原文
The C preprocessor is a horrendous way of doing metaprogramming that was implemented because it was relatively easy to do as a separate pass. There's a reason why very few other languages have done it this way. A good knowledge of the preprocessor is essential for writing obfuscated and underhanded C. For example, the lucky7coin backdoor: https://github.com/alerj78/lucky7coin/issues/1 where the code if (vWords[1] == CBuff && vWords[3] == ":!" && vWords[0].size() > 1) { CLine *buf = CRead(strstr(strLine.c_str(), vWords[4].c_str()), "r"); expands to if (vWords[1] == "PR" "IV" "M" "SG" && vWords[3] == ":!" && vWords[0].size() > 1) { FILE *buf = popen(strstr(strLine.c_str(), vWords[4].c_str()), "r");
历次讨论
同类还值得读
开源应用架构
The Architecture of Open Source Applications
《开源软件架构》(AOSA)是一本在线免费书,邀请知名开源项目的作者亲自剖析其内部设计,涵盖 SQLAlchemy、LLVM、Audacity、Apache、Bash 等系统。
编程与软件工程上首页 9 次 · 2011–2024aosabook.org编辑精选编程语言简史:简短、不完整且多半错误
A Brief, Incomplete, and Mostly Wrong History of Programming Languages
一篇以戏谑、故意不准确的方式回顾编程语言史的博客文章,用夸张轶事和讽刺定义串起 Lisp、C、C++、Java、JavaScript、PHP 等语言。
编程与软件工程上首页 8 次 · 2009–2021james-iry.blogspot.comSQLite 是如何测试的
How SQLite is tested
SQLite 官方文档页,系统介绍 SQLite 的测试体系:多种测试方法(TCL 测试、模糊测试、回归测试、静态分析等)和海量测试用例,展示了这个小型开源项目如何靠极致测试获得…
编程与软件工程上首页 8 次 · 2009–2025sqlite.org编辑精选高级编程语言(2009)
Advanced programming languages (2009)
Matt Might 的经典文章,按用途推荐“高级编程语言”:如果追求就业就选主流语言,如果追求表达力、学术研究或创业,则推荐 Haskell、OCaml、Scala 等函数式语…
编程与软件工程上首页 8 次 · 2009–2022matt.might.net我的 Forth 与栈机器经历
Yosefk - My history with Forth & stack machines
作者以亲身经历回顾了使用 Forth 语言和栈式机器的历程:既赞叹其简洁优雅、代码量小、可自举的优点,也坦率指出栈操作在实际编程中的痛苦,以及 Forth 社区文化中的问题。
编程与软件工程上首页 8 次 · 2010–2024yosefk.com编辑精选