C89/C90、C99和C11的兼容性

17
我刚刚阅读了C维基百科条目,据我所知,有三个不同的C版本广泛使用:C89、C99和C11。我的问题涉及到不同版本源代码的兼容性。 假设我要编写一个程序(使用最新版本的C11),并且导入一个使用C89编写的库。按照C11规范编译所有文件时,这两个版本是否会正常工作? 问题1: 较新的C版本,如C99、C11,是否为旧版C的超集?通过超集,我指的是,旧代码将根据新的C规范编译而没有错误,并且具有相同的含义。
我刚刚读到,在C89和C99中,双斜杠//具有不同的含义。除此特性外,C99和C11是否为C89的超集?
如果问题1的答案是否定的,则我有另外两个问题。
  1. 如何将旧代码“移植”到新版本?是否有一份说明该过程的文档?
  2. 是更好的选择C89还是C99还是C11?
提前感谢您的帮助。 编辑:将ISO C更改为C89。

2
你一直在说ISO C,但我认为你的意思是ANSI C(或C89或C90;这三个名称都指的是同一种语言)。仅ISO C本身并没有实际意义(需要注意的是,C99和C11都是由ISO语言机构定义的)。 - Cornstalks
1
@Cornstalks 从技术上讲,每个 ISO C 标准都取代了其前身,并且 ANSI 采用了 ISO C 语言标准,因此“ANSI C”也将意味着 C11。 (但大多数人不会这样使用它。) - melpomene
1
https://github.com/mauke/poly.poly/blob/master/poly.poly 可以区分 C89 和 C99(通过在每个语义上使用不同的编译方式进行编译),因此问题1的答案是“否”。 - melpomene
1
(假设你在回答我)@user3528438: 我说的是口语。当然,ANSI和ISO正式指定最新标准作为C语言的官方含义。 - Cornstalks
1
至于“ISO C”的含义,它指的是当前版本C11。C99和C90已被ISO撤回,据ISO所说不应再使用。 - Lundin
显示剩余5条评论
4个回答

16
新版本的C语言,比如C99、C11是否是旧版本的超集?
有许多差异,包括大的和微小的。大部分改变都是添加新的特性和库。C99和C11并不是C90的超集,虽然花费了很多努力来确保向后兼容。但是C11在大多数情况下是C99的超集。
但是,如果代码编写不当,从C90移植旧代码可能会导致问题。特别是各种形式的“隐式int”和隐式函数声明在C99中被禁止。C11禁止了gets函数。
完整的更改列表可以在C11草案的第13页pdf中找到,“第三版”指的是C11,“第二版”指的是C99。
如何将旧代码“移植”到新版本?是否有一份文件可以解释这个过程?
我不知道有这样的文件。如果你有良好的代码,移植就很容易。如果你有糟烂的代码,移植将会很痛苦。至于实际的移植过程,如果你了解C99和C11的基础知识,那么它将会很容易,所以最好找到一个可靠的学习来源,涵盖C99/C11的内容。
从C99移植到C11应该是轻松的。

使用C89、C99还是C11更好?

最好使用C11,因为它是当前的标准。C99和C11都包含了各种“语言错误修正”,并引入了新的有用功能。

1
谢谢你的帮助。我有另一个问题:你说使用C11更好。然后我读到了这篇文章:https://dev59.com/dmIj5IYBdhLWcg3wHh3_ 为什么Linux内核使用GNU C89方言? - Michael S
3
@MichaelS 我认为这是因为Linux是在90年代末制作的,当时C99和C11都还不存在。话虽如此,Linux内核是一个非常混乱的代码库,充斥着非标准的gcc扩展,以至于该代码永远被困在gcc中。我不知道他们为什么要以某种方式做事,但我会在其他地方寻找灵感,因为Linux内核代码库是最不规范的。同样,Linux内核代码风格指南也不是专业的编码标准。 - Lundin
谢谢你的回答和帮助!很抱歉,由于我声望不够,无法为你的评论和回答点赞。 - Michael S

1
在大多数方面,后期版本都是早期版本的超集。虽然C89代码试图使用restrict作为标识符将被C99添加具有相同拼写的保留字破坏,而且在一些情况下,那些被设计用来利用解析器某些边角情况的代码将在两种语言中被处理得不同,但其中大多数不太可能很重要。
然而,更重要的问题与内存别名有关。 C89包括限制可以用于访问某些对象的指针类型的规则。由于如果这些规则适用于由此创建的对象,像malloc()这样的函数将变得无用,因此大多数程序员和编译器编写者都将规则视为仅适用于有限情况(我怀疑如果人们不认为规则只适用于狭窄的情况,C89将不会被广泛接受)。 C99声称“澄清”了这些规则,但其新规则在效果上比旧规则的同时期解释要广泛得多,破坏了许多在这些常见解释下具有定义行为的代码,甚至一些在C89下明确定义的代码也没有实际的C99等价物。
在C89中,例如,可以使用 memcpy 将与任何类型的对象关联的位模式复制到具有相同大小的任何其他类型的对象中,在任何情况下,如果该位模式表示目标类型的有效值。 C99添加了语言,允许编译器在将 memcpy 用于将某些类型T的对象复制到没有声明类型的存储器(例如从 malloc 返回的存储器)中,并且该存储器然后被读取为不与T兼容的类型的对象时采取任意行为-即使原始对象的位模式在新类型中具有有效含义。 此外,适用于memcpy的规则也适用于将对象作为字符类型数组复制的情况-而没有明确说明这意味着什么-因此不清楚代码需要做什么才能实现与C89 memcpy 匹配的行为。
在许多编译器上,可以通过在命令行中添加 -fno-strict-aliasing 选项来解决此类问题。 请注意,指定C89模式可能不足以解决问题,因为编译器编写者通常会使用相同的内存语义,无论他们应该实现哪个标准。

0

通常情况下,新版本的标准向后兼容。

如果不兼容,您可以使用不同的标准编译不同的 .c 文件到不同的 .o 文件中,然后将它们链接在一起。这是可行的。

通常情况下,您应该为新代码使用最新的标准,并且如果可能的话,修复新标准破坏的代码,而不是使用上面的 hacky 解决方案。

编辑:除非您正在处理潜在的未定义行为。


1
标准中存在一些语义变化,导致某些构造具有定义行为(例如,在目标没有声明类型但将使用没有陷阱表示的类型进行读取的情况下,对于类型切换的某些memcpymemmove的用法)反而会调用未定义行为。在编译时规范不同的标准版本可能无法实现正确的行为,然而,如果编译器假装从未定义这样的行为,尽管旧标准中没有任何东西可以证明这种信念是正确的,那么这种情况就会发生。 - supercat

0

较新的C版本并非旧版本的严格超集。

通常情况下,这种问题仅在升级编译器或切换编译器供应商时才会出现。您必须计划大量对代码进行微调以处理此事件。许多时候,新编译器会发现旧编译器未经诊断的问题,除了通过执行较新的C标准可能发生的轻微不兼容性之外。

如果可以确定,最好使用编译器最好支持的标准。

C11维基百科文章对其与C99的差异有详细描述。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接