19

我收到了对我在 C 问题上发布的答案的评论,其中评论者建议应该编写代码以使用 C++ 编译器进行编译,因为原始问题提到代码应该是“可移植的”。

这是对“便携式C”的常见解释吗?正如我在对该答案的进一步评论中所说的那样,这让我感到非常惊讶,我认为可移植性意味着完全不同的东西,并且认为编写也是合法 C++ 的 C 代码几乎没有什么好处。

4

11 回答 11

18

当前的 C++ (1998) 标准合并了 C (1989) 标准。抛开一些关于类型安全的细则,这意味着“好的”C89 应该在 C++ 编译器中编译得很好。

问题是当前的 C 标准是 1999 年 (C99) 的标准 - 它还不是C++ 标准 (AFAIK)(*) 的正式组成部分。这意味着 C99 的许多“更好”特性(long long int、stdint.h、...)虽然受到许多 C++ 编译器的支持,但并不严格兼容。

“可移植”C 完全意味着其他东西,与官方 ISO/ANSI 标准几乎没有关系。这意味着您的代码不会对主机环境做出假设。(int 的大小、字节序、非标准函数或 errno 数字,诸如此类。)

从我曾经为一个跨平台项目编写的编码风格指南中:

跨平台 DNA(不要假设)

  • 没有本机数据类型。您可能使用的唯一数据类型是标准库中声明的数据类型。
  • char、short、int 和 long 的大小各不相同,就像 float、double 和 long double 一样。
  • int 不是 32 位。
  • char 既没有签名也没有无符号。
  • char 不能容纳数字,只能容纳字符。
  • 将短类型转换为长类型(int -> long)会破坏 CPU 的对齐规则。
  • int 和 int* 的大小不同。
  • int* 和 long* 的大小不同(与指向任何其他数据类型的指针一样)。
  • 你还记得本机数据类型甚至不存在吗?
  • 'a' - 'A' 不会产生与 'z' - 'Z' 相同的结果。
  • 'Z' - 'A' 不会产生与 'z' - 'a' 相同的结果,并且不等于 25。
  • 除了测试它的值之外,你不能对 NULL 指针做任何事情;取消引用它会使系统崩溃。
  • 涉及有符号和无符号类型的算术不起作用。
  • 数据类型的对齐规则随机变化。
  • 数据类型的内部布局随机变化。
  • 上溢和下溢的具体行为随机变化。
  • 函数调用 ABI 随机变化。
  • 操作数以随机顺序进行评估。
  • 只有编译器才能解决这种随机性。随机性将随着 CPU / OS / 编译器的下一个版本而改变。
  • 对于指针,== 和 != 仅适用于指向完全相同数据类型的指针。
  • <, > 仅适用于指向同一数组的指针。它们仅适用于 char 显式声明的 unsigned。
  • 你还记得原生数据类型不存在吗?
  • size_t(sizeof 的返回值的类型)不能转换为任何其他数据类型。
  • ptrdiff_t(从另一个指针中减去一个指针的返回值的类型)不能转换为任何其他数据类型。
  • wchar_t(“宽”字符的类型,其确切性质由实现定义)不能转换为任何其他数据类型。
  • 任何 ..._t 数据类型都不能转换为任何其他数据类型

(*):在撰写本文时确实如此。C++11 的情况发生了一些变化,但我的回答的要点是正确的。

于 2009-04-03T09:42:14.607 回答
16

不,我的回应为什么人为地将您的代码限制为 C?有一些不编译为 C++ 的符合标准的 C99 示例;早期的 C 差异较小,但 C++ 具有更强的类型和对(void)函数参数列表的不同处理。

至于使C“可移植”到C++是否有好处-该答案中引用的特定项目是基于特征的语言的虚拟机,因此不适合C++对象模型,并且有很多案例您正在提取void*解释器的堆栈,然后转换为表示内置对象类型布局的结构。为了使代码“可移植”到 C++,它会添加很多类型转换,这对类型安全没有任何作用。

于 2009-04-03T08:38:34.917 回答
8

可移植性意味着编写您的代码,以便它使用不同的编译器和/或不同的平台进行编译并具有相同的行为(即尽可能依赖 ISO 标准规定的行为)。

让它使用不同语言的编译器进行编译是一件好事(也许),但我不认为这就是可移植性的意思。由于 C++ 和 C 现在越来越不同,这将更难实现。

另一方面,在编写 C 代码时,我仍然会避免使用“类”作为标识符。

于 2009-04-03T08:40:59.653 回答
3

不,“可移植”并不意味着“在 C++ 编译器上编译”,它意味着“在任何符合标准的 C 编译器上编译”,具有一致的、已定义的行为。

并且不要仅仅为了保持 C++ 兼容性而剥夺自己对 C99 的改进。

但只要保持兼容性不束缚你的手,如果你能避免使用“类”和“虚拟”之类的,那就更好了。如果您正在编写开源代码,那么有人可能希望将您的代码移植到 C++;如果您正在招聘,您的公司/客户可能希望在未来的某个时候移植。嘿,也许你将来甚至想把它移植到 C++

做一个“好管家”,不在篝火旁乱扔垃圾,无论做什么都是善业。

请尽量避免不兼容的内容出现在您的标题中。即使您的代码从未被移植,人们也可能需要从 C++ 链接到它,因此拥有一个不使用任何 C++ 保留字的标头是很酷的。

于 2009-04-03T08:51:35.320 回答
2

这取决于。如果您正在做的事情可能对 C++ 用户有用,那么这可能是个好主意。如果您正在做一些 C++ 用户永远不需要但 C 用户可能会觉得方便的事情,请不要费心让它与 C++ 兼容。

如果您正在编写一个执行很多人所做的事情的程序,您可能会考虑使其尽可能广泛使用。如果您正在编写 Linux 内核的附加功能,您可以将 C++ 兼容性抛到脑后——它永远不会被需要。

尝试猜测谁可能会使用您的代码,如果您认为很多 C++ 粉丝可能会发现您的代码有用,请考虑使其对 C++ 友好。但是,如果您认为大多数 C++ 程序员不需要它(即它是 C++ 中已经相当标准化的功能),请不要打扰。

于 2009-04-03T08:43:37.493 回答
2

为了进行更严格的类型检查,使用 C++ 编译器编译 C 代码绝对是常见的做法。尽管有像 lint 这样的特定于 C 的工具可以做到这一点,但使用 C++ 编译器会更方便。

使用 C++ 编译器编译 C 代码意味着您通常必须用 extern "C" 块包围您的包含,以告诉编译器不要破坏函数名称。然而,这不是合法的 C 语法。实际上,您正在使用 C++ 语法,而您的代码应该是 C,实际上是 C++。使用“C++ 便利”的趋势也开始像使用未命名的联合一样蔓延。

如果你需要严格保持你的代码 C,你需要小心。

于 2009-04-03T09:05:35.460 回答
2

FWIW,一旦项目获得一定的规模和动力,它实际上可能从 C++ 兼容性中受益并非不可能:即使它不会直接移植到 C++,也确实有许多与工作/处理 C++ 源代码相关的现代工具代码。

从这个意义上说,缺乏与 C++ 的兼容性实际上可能意味着您可能必须想出自己的工具来做特定的事情。我完全理解在某些平台、环境和项目中支持 C 而不是 C++ 的原因,但从长远来看,C++ 兼容性通常会简化项目设计,最重要的是:它提供了选项

此外,有许多 C 项目最终变得如此庞大,以至于它们实际上可能受益于 C++ 的功能,例如使用带有访问修饰符的类改进了对抽象和封装的支持。

以 linux(内核)或 gcc 项目为例,它们基本上都是“仅限 C”的,但两个开发人员社区仍然定期讨论切换到 C++ 的潜在收益。

事实上,目前正在进行 gcc 工作(FSF 树中!)将 gcc 源代码移植到有效的 C++ 语法中(有关详细信息,请参阅:gcc-in-cxx),以便可以使用 C++ 编译器来编译源代码代码。

这基本上是由长期的 gcc 黑客发起的:Ian Lance Taylor。

最初,这只是为了提供更好的错误检查以及改进的兼容性(即一旦完成此步骤,这意味着您不一定必须有 C 编译器来编译 gcc,您也可以只使用一个 C++ 编译器,如果你碰巧“只是”一个 C++ 开发人员,这就是你得到的)。

但最终,这个分支旨在鼓励将 C++ 作为 gcc 的实现语言迁移,这是一个真正具有革命性的步骤 - 一种范式转变,这些 FSF 人员正在批判性地看待这种转变。

另一方面,很明显 gcc 已经受到其内部结构的严重限制,任何至少有助于改善这种情况的事情都应该受到称赞:开始为 gcc 项目做贡献是不必要的复杂和乏味的,主要是由于复杂的内部结构,已经开始使用宏和 gcc 特定扩展来模拟 C++ 中的许多更高级的功能。

为最终切换到 C++ 准备 gcc 代码库是最合乎逻辑的事情(不过,无论何时实际完成!),实际上需要它来保持竞争力、有趣和简单相关,这适用于特别是由于非常有前途的努力,例如 llvm,它们并没有带来所有这些麻烦和复杂性。

虽然用 C 编写非常复杂的软件通常是可能的,但这样做会变得不必要地复杂,许多项目在很久以前就已经完全超越了 C。这并不意味着 C 不再相关,恰恰相反。但是考虑到一定的代码库和复杂性,C 根本就不一定是完成这项工作的理想工具。

实际上,您甚至可以争辩说 C++ 不一定是某些项目的理想语言,但只要一种语言本身支持封装并提供实施这些抽象的方法,人们就可以绕过这些限制。

仅仅因为显然可以用非常低级的语言编写非常复杂的软件,并不意味着我们必须这样做,或者它首先确实有效。

我在这里谈论的是具有数十万行代码的复杂软件,其生命周期为几十年。在这种情况下,选择变得越来越重要。

于 2009-05-20T09:16:12.113 回答
2

不,口味问题。我讨厌强制转换 void 指针,使代码混乱,没有太多好处。

char * p = (char *)malloc(100);

对比

char * p = malloc(100);

当我编写一个“面向对象”的 C 库模块时,我真的很喜欢使用“this”作为我的对象指针,因为它是一个 C++ 关键字,它不会在 C++ 中编译(这是故意的,因为这些模块在 C++ 中毫无意义鉴于它们确实存在于 stl 和库中)。

于 2010-01-06T13:54:57.470 回答
1

为什么你看不到什么好处?这很容易做到,谁知道您将来会如何使用该代码。

于 2009-04-03T08:37:11.643 回答
1

不,可被 C++ 编译并不是对可移植性的常见解释。处理非常旧的代码,K&R 风格的声明是高度可移植的,但不能在 C++ 下编译。

正如已经指出的,您可能希望使用 C99 增强功能。但是,我建议您考虑所有目标用户并确保他们可以使用这些增强功能。不要只使用可变长度数组等,因为你有自由,但只有在真正合理的情况下。

是的,尽可能地保持 C++ 兼容性是一件好事——其他人可能有充分的理由需要将 C 代码编译为 C++。例如,如果他们想将它包含在 MFC 应用程序中,他们将不得不在单独的 DLL 或库中构建纯 C,而不仅仅是能够将您的代码包含在单个项目中。

还有一个论点是,在 C++ 模式下运行编译器可能会发现细微的错误,这取决于编译器,如果它应用了不同的优化。

于 2009-04-03T09:04:46.053 回答
1

AFAIK 经典文本中的所有代码 C 编程语言第二版可以使用标准 C++ 编译器(如 GCC (g++))进行编译。如果您的 C 代码符合该经典文本中遵循的标准,那么就足够了,并且您已准备好使用 C++ 编译器编译您的 C 代码。

以 linux 内核源代码的实例为例,它大部分是用 C 语言编写的,带有一些内联汇编代码,用 C++ 编译器编译 linux 内核代码是一场噩梦,因为“new”被用作变量名的最不可能的原因linux内核代码,其中C++不允许使用'new'作为变量名。我这里只举一个例子。请记住,Linux 内核是可移植的,在 intel、ppc、sparc 等架构中可以很好地编译和运行。这只是为了说明可移植性在软件世界中确实具有不同的含义。如果您想使用 C++ 编译器编译 C 代码,您正在将代码库从 C 迁移到 C++。我认为它是两种不同的编程语言,最明显的原因是 C 程序员不太喜欢 C++。但我喜欢他们两个& 我经常使用它们。您的 C 代码是可移植的,但您应该确保在将 C 代码迁移到 C++ 代码时遵循标准技术以使 C++ 代码具有可移植性。继续阅读以了解您从哪里获得标准技术。

您必须非常小心地将 C 代码移植到 C++ 代码中,我要问的下一个问题是,如果某些 C 代码可移植且运行良好且没有任何问题,您为什么还要费心去做呢?我不能接受可管理性,Linux 内核又是一个 C 语言的大代码源被很好地管理。

始终将两种编程语言 C 和 C++ 视为不同的编程语言,尽管 C++ 确实支持 C 并且其基本概念是始终支持这种出色的语言以实现向后兼容性。如果您不将这两种语言视为不同的工具,那么您将陷入流行、丑陋的 C/C++ 编程语言大战,并让自己变得肮脏。

选择可移植性时使用以下规则:

a) 您的代码(C 或 C++)是否需要在可能使用本机 C/C++ 编译器的不同架构上编译?b) 研究您希望运行程序并计划代码移植的不同架构上的 C/C++ 编译器。在这上面花点时间。c) 尽可能尝试在 C 代码和 C++ 代码之间提供一个干净的分离层。如果您的 C 代码是可移植的,您只需使用可移植 C++ 编码技术再次围绕该可移植 C 代码编写 C++ 包装器。

转向一些关于如何编写可移植 C++ 代码的优秀 C++ 书籍。我个人推荐 Bjarne Stroustrup 本人的 C++ 编程语言、Scott meyers 的 Effective C++ 系列以及 www.ddj.com 上的热门 DDJ 文章。

PS:我帖子中的Linux内核示例只是为了说明可移植性在软件编程中确实意味着不同的含义,并不批评Linux内核是用C而不是C++编写的。

于 2009-04-03T09:13:15.660 回答