将C语言源代码转换为C++

45
你如何将一个相当大(> 300K),相对成熟的 C 代码库转换为 C++?
我建议采用以下一般策略:
1.将所有代码编译为 C++ 的 C 子集,并使其正常工作。
2.将模块转换为巨大的类,以便所有交叉引用都由类名范围限定,但保留所有函数和数据作为静态成员,并使其正常工作。
3.将巨大的类转换为具有适当构造函数和初始化交叉引用的实例;根据需要替换静态成员访问为间接访问,并使其正常工作。
4.现在,将项目视为面向对象应用程序,并写出依赖项可跟踪的单元测试,并在依赖项不可跟踪时将其分解为单独的类。在每个转换步骤中,目标都是从一个工作程序移动到另一个工作程序。
该程序是一个编译器,可能有数百万其他程序依赖于其行为不变,因此完全重写基本上不是一种选项。源代码接近20年,每年可能有30%的代码更改,因此其中一个目标是提高可维护性。
请注意:为了问题的简化,假设必须将其转换为C ++,而将其保留在C中不是一个选项。

强制迁移的时间框架是多久? - oz10
你对 C 代码库了解得如何?内外都熟悉吗? - oz10
时间框架可能为1-10年(这是一个长期存在的程序)。 - Barry Kelly
1
哦,我看到你的编译器是什么了! - Federico A. Ramponi
1
只是出于好奇,你为什么想把你的C代码翻译成C++?留在C中有什么问题吗? - Chris Lutz
11个回答

15

刚几个月前,我也在做一件类似的事情(在一个十年前用“C++只是带智能struct的C”哲学编写的商业项目上)。我建议您采用吃大象的策略:一口一口地吃。 :-)

尽可能将其分成阶段,以便对其他部分影响最小。如Federico Ramponi所建议的,构建一个外观系统是一个不错的开始-一旦所有内容都有了C ++门面,并通过它进行通信,您就可以更改模块的内部而相当确定他们不会影响它们之外的任何内容。

由于我们之前进行过较小的重构工作,因此我们已经有了一部分C++接口系统,所以这种方法在我们的情况下并不困难。一旦我们让所有内容都以C++对象进行通信(这需要几周时间,在完全分离的源代码分支上工作,并在获得批准时将所有更改集成到主分支中),我们很少遇到不能在离开前编译出完全工作版本的情况。

转换还没有完成-我们已经暂停两次进行过渡版本发布(我们每隔几周发布一个点版本),但它已经在不断进展,并且没有客户抱怨任何问题。我们的QA人员也只发现了我记得的一个问题。 :-)


听起来有点吓人...你应该写一篇更详细的文章介绍这个过程,我敢打赌它会受到很多关注。 - Ape-inago
我写了几篇关于转换特定部分的博客文章,链接在这里:http://geekblog.oakcircle.com/2008/07/19/ascii-unicode-and-windows/ 和 http://geekblog.oakcircle.com/2009/03/15/superbug/。虽然我不是一个有趣的作家,无法使整个内容有趣。 - Head Geek

12

关于:

  1. 将所有内容编译为C++的C子集并使其正常工作,和
  2. 实现一组外观,不改变C代码。

为什么“必须将其转换为C ++”?您可以在不将其转换为大量类等的情况下包装C代码。


将代码更模块化、转换为C++并添加单元测试的一个重要目的是使其更易于维护。仅仅在前面加上一个外观是不够的。 - Barry Kelly
“必须翻译为C ++”是为了淘汰那些回答“不改变C”的答案。 - Barry Kelly
也许这段 C 代码不是纯的 Ansi C,而是一些古老的 C 方言,不是纯 ANSI。 :-) - Warren P

7

您的应用程序有很多人在工作,不想出现故障。

如果您严肃考虑大规模转换为面向对象的编程风格,需要使用大规模转换工具来自动化工作。

基本思路是将数据分组为类,然后让工具重构代码以将该数据移入类中,将仅针对该数据的函数移入这些类中,并修改所有访问该数据的地方,使其调用这些类。

您可以进行自动预分析,形成统计聚类以获取一些想法,但仍需要一个应用程序工程师来决定哪些数据元素应该分组。

能够执行此任务的工具是我们的DMS软件重构工具包。DMS拥有强大的C解析器,用于读取您的代码,将C代码捕获为编译器抽象语法树(与传统编译器不同),并且可以计算整个300K SLOC的流分析。DMS还可以使用C++前端作为“后”端;编写将C语法映射到C++语法的转换。

一个大型航空电子系统上的主要C ++重构任务展示了使用DMS进行此类活动的情况。请参见www.semdesigns.com/Products/DMS/DMSToolkit.html上的技术论文,特别是通过自动程序转换重新设计C ++组件模型

这个过程不适合胆小的人。但任何考虑手动重构大型应用程序的人已经不害怕辛苦工作了。

是的,我与公司有关联,是其首席架构师。


2
不错的帖子,但你可能想要添加一句话说明你与提到的产品和公司有关联,否则人们会通过称你的帖子为隐蔽广告来指出这一点。;-) - none
3
我经常对人们在谈论大规模改变技术时提出的“成本/风险”论点感到惊讶,因为他们未能将其与手动方法的“成本/风险”进行比较。手动实现这样的应用程序所需的成本必须非常高,而风险当然是程序员会不正确地重组代码。这项技术的风险在于其准确地重组代码的能力......而我们在这方面已经相对超越了这一点。现在,使用工具的成本/风险确实比保持不变要高得多,但这也有其他成本。 - Ira Baxter
我认为DMS是一个非常有趣的产品,但它似乎针对非常特定的用途/用户(可能主要是由于成本问题)? 此外,通过在网页上提供某种限制版本的网络访问(例如,允许人们通过某些Web表单上传5-10 kbytes的代码并执行一些特定的C<->C ++转换,如“重命名符号”),可能更容易了解其潜力。 - none
2
如果你可以在一天或一周内手动解决问题,那么你不需要DMS。如果你正在考虑需要数月时间完成工作,或者你需要非常确定你没有破坏代码,那么你需要像DMS这样的工具。 - Ira Baxter
很遗憾,SO的访问者不理解SO约定的惯例。 "我们"是一种已经通过MetaStackoverflow讨论确定的隶属关系指示。 - Ira Baxter
显示剩余2条评论

5
我会选择使用C++类来替代C接口。不去修改C代码会降低出错的几率,同时也能显著加快进程速度。
一旦你完成了C++接口的搭建,将代码复制粘贴到你的类中就是一项微不足道的任务。正如你提到的那样,在这一步骤中进行单元测试至关重要。

4
“C接口”的起点和终点都是“main()”。我认为你可能漏掉了几个步骤… :) - Barry Kelly

4

GCC 正在从 C 转向 C++ 中间阶段。他们首先将所有内容移入 C 和 C++ 的公共子集中。在此过程中,他们为找到的一切添加了 GCC 警告,并使用 -Wc++-compat 选项进行管理。这应该可以帮助你完成旅程的第一部分。

在完成使用 C++ 编译器编译所有东西后,我会专注于替换那些在 C++ 中有对应语法的内容。例如,如果你正在使用 C 宏定义的列表、映射、集合、位向量和哈希表等数据结构,你很可能通过将其转换为 C++ 语言获得更多好处。同样,对于面向对象编程方面,如果你已经使用了类似于 C 的面向对象机制(如结构体继承),那么在 C++ 中,你很可能会发现更好的代码清晰度和类型检查。


3

除了我建议您首先检查测试套件并尽可能使其紧密,然后再进行任何编码外,您的列表看起来还不错。


1
测试套件非常严格,相信我。20年的QA经验,数以万计的已记录缺陷和由QA编写的测试用例使得它变得如此。 - Barry Kelly

3

让我们提出另一个愚蠢的想法:

  1. 将所有东西编译为C++的C子集并使其正常工作。
  2. 从一个模块开始,将其转换为一个巨大的类,然后转换为一个实例,并构建一个C接口(与您最初使用的接口相同)。让剩余的C代码使用该C接口。
  3. 根据需要进行重构,逐个模块地将OO子系统从C代码中扩展出来,并在它们变得无用时删除C接口的部分。

1
是的,那大概是我的计划的1和2部分,更详细地分解了。 - Barry Kelly

3
除了你想如何开始之外,还要考虑的两个问题是你想要专注于什么和你想要停止的位置。
你提到代码变化很大,这可能是你努力工作的关键。我建议你选择需要大量维护的代码部分,成熟/稳定的部分显然已经能够正常工作,所以最好将它们保持不变,除非是对外观等方面进行一些修饰。
想要停止的位置取决于想要转换为C++的原因。这不可能成为一个目标本身。如果这是由于某个第三方依赖关系,重点放在与该组件的接口上。
我工作的软件是一个巨大的、古老的代码库,在多年前从C转换成C++。我认为这是因为GUI被转换为Qt。即使现在它仍然大部分看起来像一个带有类的C程序。由于公共数据成员引起的依赖关系断裂,将过程性怪兽方法的大型类重构为更小的方法和类从未真正起飞,我认为有以下几个原因:
1.没有必要改动那些工作正常且不需要增强的代码。这样做会引入新的错误,而且最终用户并不欣赏。
2.重构很难以可靠的方式进行。许多代码片段如此之大,也如此重要,以至于人们几乎不敢触及它。我们有一个相当广泛的功能测试套件,但足够的代码覆盖率信息很难得到。因此,很难确定是否已经有足够的测试来在重构期间检测问题。
3.回报率难以确定。最终用户不会从重构中受益,所以必须减少维护成本,这在初期会增加,因为通过重构你会在成熟的、相当无错的代码中引入新的错误。而且重构本身也是代价高昂的……
注:我想你知道《与遗留代码有效地工作》这本书吧?

1
是的,我有这本书。不幸的是,它几乎完全只适用于可进行单元测试的代码。对于使用非面向对象编程的人来说,主要建议 - 我记得只有不到一个段落 - 是使用面向对象的变体。 - Barry Kelly
1
关于大部分仍然像C语言一样的信息,我可以接受。正如我所说,有相当多的变革,因此在未来重写的部分中使用C++仍然是模块化方面的优胜选择。 - Barry Kelly
实际上,除了内存管理器之外,并没有真正的“稳定”部分。主要目标是通过谨慎使用模板、类来提高新编写的源代码抽象级别,并减少交叉依赖,特别是由全局变量引起的依赖关系。 - Barry Kelly
代码的变动很大;某些功能子集被全面重写并不罕见。例如,需要为64位重新编写代码生成器,为Unicode适配扫描器,为泛型方法推断调整重载等等。 - Barry Kelly

2
你提到你的工具是一个编译器,并且说:“实际上,在多重分派中,模式匹配不仅限于类型匹配,这会更好。” 你可能想看一下 maketea。它为AST提供了模式匹配功能,以及来自抽象语法的AST定义、访问者、转换器等。

1

如果您有一个小型或学术项目(比如说少于10,000行),重写可能是最好的选择。您可以根据自己的需要对其进行分解,这不会花费太多时间。

如果您有一个真实世界中的应用程序,我建议将其编译为C++(通常意味着主要是修复函数原型等),然后开始重构和面向对象包装。当然,我不认同代码必须是面向对象结构才能成为可接受的C++代码的哲学。我会逐个转换每一部分,根据需要进行重写和重构(用于功能或集成单元测试)。


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