如何编写可移植的C++代码?

32

我作为一个C++初学者,想从一开始就练习编写可移植的代码,有哪些需要注意的事项?

谢谢。


2
这看起来像是一个社区维基问题。 - bta
4
为什么会这样?如果有人花了30分钟时间写了一个好答案回答这个问题,当它被点赞时,他们难道不应该得到声望值吗? - jalf
1
你想把它移植到哪里?如果您计划将其移植到不同的操作系统、不同的架构中,那么将存在很大的差异。因此,每个移植都有一个答案。例如,在8位甚至16位微型嵌入式系统中工作时,您应该避免使用这里推荐的任何库。所以,请您能具体一些吗? - Hernán Eche
4
@jalf- 社区维基并不意味着贡献者不应得到认可。这只是意味着没有单一的“正确”答案(就像这里,问题是一个主观请求寻求一般建议)。每个回答都为问题的集体答案做出了贡献,超越了任何单一的贡献范围。您仍然可以对社区维基问题的回答进行投票。 - bta
5
所以你的意思是贡献者“应该”得到赞誉,但不应该获得吗?为什么?只是为了在旁边获得一个任意的“社区维基”标签?任何人从中获得什么?这为什么是好事?我们会失去一些好处(奖励那些写出好答案的人),因此应该有其他好处来弥补这一点。那是什么?唯一的优点就是让CW团体内部感觉温暖和愉悦。不,讨论应该发生在任何人建议实际进行CW的问题的地方。 - jalf
显示剩余2条评论
12个回答

19
  • 学习使用标准库
  • 阅读书籍(例如 这本书)
  • 当您具备经验时,学习使用 boost

4
同时,像这样的书籍:https://dev59.com/_3RC5IYBdhLWcg3wK9yV - Mike Seymour
2
那么,Boost与可移植性有什么关系呢? - SigTerm
7
@SigTerm: Boost的主要设计目标之一是可移植性。它可以在各种平台上运行,并且很好地抽象出了差异。例如,线程库和文件系统库以平台无关的方式工作。其中一些功能已经包含在C++0x中。 - rmeador
1
@SigTerm:boost 不仅仅是一个项目,它是许多项目,很可能比标准库更强大。它被广泛使用(因为它以跨平台的方式提供了大量有用的功能),已经成为事实上的标准,事实上,当 C++0x 发布时,其中的几个模块将被列入实际的标准库中(许多编译器/库已经在 std 命名空间下支持 boost 构造)。但是,无论怎样,你可以踩我,这不会影响我的声誉 :P - rmeador
2
@Sig:虽然Boost变得越来越相互依赖,但仍由许多单独的库组成,您只能使用其中的一些。其中一些,例如 shared_ptr,除非已经有内部替代方案,否则应该成为事实上的标准。无论如何,您为什么认为它不适合小项目?只需包括您要使用的内容,如果需要分发源代码,则只需打包相关部分即可。 - Georg Fritzsche
显示剩余3条评论

17
我应该注意些什么来编写可移植的代码?
  1. 准备几个编译器并定期在目标平台上测试代码。如果你正在为Windows / Linux编写跨平台软件,请准备mingw、visual studio express(即"微软编译器")和一个带有g++的Linux安装(或使用虚拟机)。即使你的代码完美无缺,编译器可能会有某种意外的问题。例如,某些版本的ms编译器对字符串常量的大小有限制,而gcc没有。
  2. 不要依赖标准类型的大小。例如,在MSVC上,sizeof(wchar_t)是2字节。在linux安装中,它可以是4字节。使用sizeof(如果需要),或者尽量避免在代码中使用任何类型的大小。而且你不应该假设指针的大小是4个字节(将用户数据指针传递到api调用场景中)-在64位上它将是8个字节。
  3. 不要使用特定于编译器的pragma、宏和扩展。例如,避免使用“#pragma once”。
  4. 不要使用标准库的扩展(由编译器开发人员提供)。虽然这更适用于C库函数。例如,MS编译器提供了多个标准C样式例程的“安全”版本(如strcpy_s)。当然,在其他平台上这些不可用。
  5. 如果你决定在C++代码中使用C样式例程(如sprintf),要非常小心。(我知道这是不好的做法,但在某些情况下这很有用)它们有稍微不同的实现、扩展和不同数量的参数。例如,sprintf可能会有不同的附加格式,在vswprintf例程中在msvc和gcc上实现不同。
  • 不要依赖编译器特定的数据类型,例如 __int32。你很可能需要一些保证为4个字节长(或类似长度)的类型 - 使用typedef结合条件编译(“#ifdef WIN32”), 或者使用跨平台库提供的类型。例如,SDL提供了像Uint8、Qt 4提供了quint32这样的类型。这是相当常见的做法。
  • 避免直接调用操作系统函数。使用标准函数来访问文件。
  • 当你必须使用特定于操作系统的调用时,请使用条件编译(#ifdef WIN32等)。
  • 尽量在所有平台上使用相同的构建系统。Linux上没有MSBuild。使用gnumake、cmake、scons或qmake。虽然在其中一些系统中,您将不得不编写适用于不同编译器的标志,但在任何地方都可以使用相同的脚本。例如,它与SConstructs配合得很好。维护一个所有平台的构建脚本可能比在不同的构建系统之间同步更改更容易。
  • 对于所有需要与操作系统交互的操作(GUI、文件操作),请使用跨平台库。Qt是一个不错的选择。

  • +1 给一些好的观点。实际上,在 Linux 上有一个 MSBuild,但可能没有人用它来编写 C++ 代码:http://www.mono-project.com/Microsoft.Build... 当您选择“标准”函数时,请注意有不止一个标准可供选择,并且并非所有标准函数都是可移植的,正如我最近所了解的那样:https://dev59.com/fmLVa4cB1Zd3GeqPsQbi - Qwertie
    1
    关于第8个条目,我想补充一下:不要在代码中散布#ifdef。最好创建一个类似“platform.h”的头文件,并将平台相关的代码封装到平台无关的函数中,并将这些函数聚集在一起。换句话说,扩展编译器和库以获取代码缺失的可移植性功能,然后使用可移植的方式编写其余部分的代码。 - Spike0xff

    15

    请将特定平台的代码与可重用代码分离,最好放在不同的文件中,但至少应该放在不同的函数中。如果你到处都有 #if WIN32#if CYGWIN#if BSD,那么维护就成了噩梦。

    然后,请尽早并经常在至少两个不同的平台上进行编译。典型选择是Windows上的Visual C++和Linux上的gcc。由于系统库和编译器都不共享,因此您会在非可移植代码深入设计之前就捕捉到它们。


    2
    自动化您的构建,使用类似Hudson的工具,它可以按计划(每小时/每天/在CVS检入时等)进行构建,并在构建失败时发送电子邮件。您可以使用虚拟机,在一台PC上为Windows和Linux构建。 - Mawg says reinstate Monica
    我会添加针对ARM的交叉编译,比如Android。 - vesperto

    5

    首先从编写命令行程序开始。准备好之后,寻找一个跨平台的窗口工具包(如Qt)。

    如果您有意撰写多语言代码,请使用第三方unicode库(如ICU),而不是依赖于特定于平台的库。


    2
    如果你处理文本,一定要关注多语言。 - Steven R. Loomis
    2
    ICU是一个非常好的点,因为C++没有真正的(Unicode-aware)字符串数据类型。在类Unix系统上,std::string通常可以工作,在Windows上,std::wstring总是可以工作,但对于真正的跨平台程序,您需要像ICU的UnicodeString这样的真正的字符串数据类型。 - Philipp

    4

    尽可能使用STL类型。注意不要使用依赖于系统的类型和API。例如,在Windows上不要使用UINT和DWORD等类型。

    您可以使用像boost这样的库来使编写可移植代码更容易。如果需要GUI,请考虑使用跨平台工具包,如Qt。

    有时您需要编写特定于平台的代码,在这种情况下,您可以这样做:

    #ifdef _WIN32
    #include <windows.h>
    #else
    #include <unistd.h>
    #endif
    

    3

    虽然已经有人提到过这个问题,但以下是我的看法:

    1)您需要使用C++吗?由于它接近底层,所以不是写可移植代码的最佳语言。Java、Python、Perl、PHP或Javascript可能更适合您。

    2)如果您需要使用C++,不要试图编写完全可移植的代码,因为这几乎是不可能的。相反,请尽早决定要支持哪些平台。例如:Linux、MacOS X、Windows。

    3)确保您在所有选择的平台上持续测试代码。不要只在Windows上构建并期望“完成后”仅编译Linux版本。每天在所有平台上编译,并确保您持续测试以查找问题。


    1
    除了#2之外,从在两个平台上编译到在超过两个平台上编译通常更容易,而从一个平台到两个平台则更加困难。添加第二个平台将有助于发现大部分可移植性问题。 - KeithB
    1
    对于项目#1,点赞。如果您想要可移植性,C ++ 是最糟糕的选择之一,几乎所有其他语言,甚至是Bash或Visual Basic,都更具可移植性。 - Philipp
    2
    有时候,与“裸金属”更接近比高级更具可移植性。当我为嵌入式系统编写代码时,没有库的C语言是最具可移植性的语言,为什么?因为每个平台都有编译器,并且它可以在微小的环境中工作。C++是下一个选择,但并非每个微控制器都有编译器,所以我会再次问,“你想要将其移植到哪里?” - Hernán Eche
    @HernánEche,C++不太可移植的原因是由于不同的编译器。Visual C++和GCC在许多事情上经常存在分歧,特别是当涉及到更新的C++标准(如C++0x、C++1z等)时。 - trve.fahad

    2

    不谨慎的程序员很可能会陷入许多陷阱中,我们可以试图对其进行分类。但首先要告诉你的是:绝对不可能。

    问题在于,即使符合标准的代码也可能因为特定编译器问题而无法移植。

    现在我能想到的主要类别如下:

    编译器扩展

    比如使用变量数组:

    void func(int const n)
    {
      int array[n];
    }
    

    这不是标准做法,但许多编译器仍然支持它,因为它很实用。

    标准库扩展

    许多标准库实现提供了一个从未被指定的std::hash_map。如果您在代码中使用它,则无法移植。

    现代趋势是将这些内容存储到std::tr1命名空间中,以便程序员知道这是一种扩展。

    还要注意,许多定义了非通用的typedef或宏(例如PRETTY_FUNCTION)。标准没有指定任何宏,而且很少有typedef

    平台特定

    例如,intdouble的大小和对齐方式在标准中没有指定。如果您进行位操作并期望它具有32位,则在64位平台上,即使不更改编译器,也会出现问题。

    平台API

    我们的程序旨在被编译,并且通常旨在与运行它们的计算机交互:

    • 访问硬件
    • 访问文件系统
    • 访问屏幕

    您需要找到跨平台的可移植API,或自己编写。检查下面列表中的一些库。

    大多数编写良好的库在很大程度上都是可移植的,只需确保它们支持:

    • 您感兴趣的编译器
    • 您感兴趣的平台

    好的库涉及以下内容:

    • Apache(库集合)
    • Boost
    • Qt(用于图形)
    • ICU(用于Unicode处理)

    其他库需要您进行评估...这需要时间。

    我认为那里没有完美的答案。但是,由于完全可移植性是不可能的,因此您需要决定要支持哪些编译器和平台。

    对于平台,您应该从Windows和一个Linux版本开始。对于编译器,请选择任意两个(如果可以负担得起,则使用Comeau)。


    可变长度数组最可能被支持,因为它们是基于 ISO C 99 标准而非实用性原因。 - ninjalj
    2
    很多编译器根本没有实现C99(例如,MSVC)。变长数组不是C++。 - Alexandre C.
    实际上,我渴望使用可变参数宏,但它们不是C++的一部分 :( - Matthieu M.

    2

    在C++中编写与操作系统无关的代码非常困难。看看这个简单的例子:

    #include <iostream>
    int main(int argc, char** argv) {
      std::cout << argv[0] << std::endl;
    }
    

    这是完全有效的C++代码,但它不可移植,因为在Windows上它不会接受Unicode命令行参数。在Windows上正确的版本应该是:

    #include <iostream>
    int wmain(int argc, wchar_t** argv) {
      std::wcout << argv[0] << std::endl;
    }
    

    当然,这又是不可移植的,只能在Windows上工作,并且不符合标准。所以实际上,即使是在C++中编写可移植的main()函数,也必须使用条件编译。

    这会在任何平台上都会引起麻烦。使用std::endl代替,它会刷新缓冲区并实际产生一些输出。我无法计算所有问题的数量,这些问题归结为人们没有刷新cout - Ben Voigt
    1
    @Alexandre:没错,C++本身对字符串或Unicode一无所知,但因为Windows使用UTF-16字符串,而且在Windows上wchar_t始终是一个16位无符号整数(否则没有程序会编译通过),所以如果你想在Windows上支持Unicode,可以使用wchar_t。 @Ben:谢谢,我会进行更正。 - Philipp
    实际上,如果您想在Windows上支持Unicode,就必须使用wchar_t(也称为WCHAR或TCHAR+_UNICODE)。Windows的API和标准库不能将8位字符字符串识别为UTF-8。这对于编写可移植代码非常不方便(除非只关心ASCII),因为Linux系统似乎一般不支持wchar_t字符串(而且您也不会希望支持,因为wchar_t是4个字节,因此存储字符串的效率很低)。 - Qwertie

    2

    一些指导方针:

    1. 将代码的业务端和GUI分开。
    2. 避免使用编译器特定的支撑(#pragma等)。
    3. 使用常规表达式,而不是可爱的位操作技巧,这样行为就不会因编译器/平台而改变。
    4. 如果涉及硬件,则应放在设备驱动程序中。
    5. 使用数据类型头文件,如types.h(uint32_t等)。
    6. 使用操作系统抽象层,这样你就不会直接调用操作系统调用。

    有时为了获得可移植性,你需要在效率和性能之间进行权衡。例如,如果你的代码需要访问缓冲区中的字段,你可以将紧凑的结构体强制转换为缓冲区指针。但那非常不可移植。所以,你需要使用通过偏移计算出来的命名指针--有时还要处理边界对齐处理代码。虽然不美观,但却是可移植的。幸运的是,你可以通过巧妙使用类接口来隐藏大部分这些内容。

    并非所有代码都需要按照这种方式编写。如果你以非常模块化的方式设计应用程序,并定义明确的责任范围,那么90-95%的代码就可以在不痛苦的情况下实现可移植性。然后只需将5-10%的代码隔离在一个非常局部化的区域中,以便为新平台进行定制。


    1

    在学习过程中,尽量避免只关注一种实现的书籍。有些书的导言或早期章节会提供有关如何获得或使用语言实现的说明;如果它提到了多个实现,那么你可能没问题。

    获取一个独立于平台的参考书。 Stroustrup 的《C++ 程序设计语言》是一本很好的参考书,但对于初学者来说并不适合当作入门书籍。不要依赖于特定实现的参考书。例如 MSDN 很有用,但它的主要关注点是如何使用 Visual C++ 编写 Windows 程序,而不是如何编写能够在任何地方编译和运行的程序。

    为了编写任何真正有用的东西,你需要进入非可移植代码领域。试着养成将用户界面代码与其他所有代码分开的习惯,因为这是你的兼容性最小的地方。你要改变的代码越少,你的代码就越具有可移植性。


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