如何用c ++编写可移植代码?

时间:2010-06-23 16:30:06

标签: c++ portability

编写可移植代码时我应该记住哪些事项?因为我是一名c ++初学者,所以我想从一开始就练习它。

感谢。

12 个答案:

答案 0 :(得分:17)

  • 学习使用标准库
  • 阅读书籍(例如this one
  • 当您有经验时,请学会使用boost

答案 1 :(得分:14)

将特定于平台的代码与可重用代码分开,最好是在不同的文件中,但至少在不同的函数中。如果您开始使用#if WIN32以及#if CYGWIN#if BSD,那么您将面临维护噩梦。

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

答案 2 :(得分:13)

  

编写可移植代码时我应该记住哪些事项?

  1. 在附近保留多个编译器,定期在目标平台上测试代码。如果你正在为windows windows / linux做跨平台软件,请保持mingw,visual studio express(即“microsoft compiler”)和使用g ++(或使用虚拟机)的linux安装。即使你的代码是完美的,编译器也可能有某种意想不到的怪癖。例如,某些版本的ms编译器对字符串常量的大小有限制,而gcc没有。
  2. 不要依赖标准尺寸。例如,在msvc上,sizeof(wchar_t)是2个字节。在linux上安装它可以是4个字节。使用sizeof(如果需要),或者尽量避免在代码中使用任何类型的大小。并且您不应该假设指针大4个字节(将用户数据指针传递到api调用方案) - 它将是64位上的8个字节。
  3. 不要使用特定于编译器的编译指示,宏和扩展。例如,避免“#pragma once”。
  4. 不要使用标准库的扩展(由编译器开发人员提供)。但是,这更适用于C库函数。例如,MS编译器提供标准C样式例程的多个“安全”(如strcpy_s)版本。当然,这在其他平台上不可用。
  5. 如果您决定在C ++代码中使用C风格的例程(如sprintf),请务必小心。 (我知道它应该是一个不好的做法,但在某些情况下这很有用)它们的实现,扩展和不同数量的参数略有不同。例如,sprintf可能具有在不同平台上以不同方式实现的不同附加格式。例如,我上次检查“%S”在vsvprintf例程中对msvc和gcc的行为有所不同。
  6. 不要依赖特定于编译器的数据类型,例如__int32。很可能你需要某种类型,保证长度为4个字节(或类似的东西) - 使用typedef结合条件编译(“#ifdef WIN32”)。或使用跨平台库提供的类型。例如,SDL提供的类型如Uint8,Qt 4有quint32等。这是很常见的做法。
  7. 避免直接操作系统调用。使用标准功能访问文件。
  8. 当您必须使用特定于操作系统的调用时,请使用条件编译(#ifdef WIN32等)
  9. 尝试在所有平台上使用相同的构建系统。 Linux上没有MSBuild。使用gnumake,cmake,scons或qmake。虽然在某些系统中你必须使用不同编译器的标志进行编码,但是可以在任何地方使用相同的脚本。例如,它与SConstructs很好地配合。为所有平台维护一个构建脚本可能比在不同构建系统之间同步更改更容易。
  10. 对于需要与操作系统交互的所有操作(Gui,文件操作),请使用跨平台库。 Qt是个不错的选择。

答案 3 :(得分:5)

开始编写命令行程序。准备好后,找到一个跨平台的窗口工具包,例如Qt

如果您对编写多语言代码感兴趣,请使用第三方unicode库,例如ICU,而不是依赖于特定于平台的库。

答案 4 :(得分:4)

尽可能使用STL类型。请注意使用系统相关类型和API。例如,不要在Windows上使用类似UINT和DWORD的类型。

您可以使用类似boost的库,以便更轻松地编写可移植代码。如果您需要GUI,请考虑使用Qt等跨平台工具包。

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

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

答案 5 :(得分:2)

粗心的程序员可能会走进很多陷阱,我们可以尝试对其进行分类。但是让我先告诉你:绝对是不可能的。

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

现在,我可以在脑海中思考这些主要类别。

编译器扩展程序

例如使用变量数组:

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

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

标准库扩展

许多标准库实现提供了从未指定的std::hash_map。如果您在代码中使用它,它就不可移植。

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

另请注意,许多人定义typedef或非通用的宏(例如PRETTY_FUNCTION)。标准没有指定宏,并且很少有typedef。

特定于平台

例如,标准中未指定intdouble的大小和对齐方式。如果你做了一些比特,并期望它有32位,即使不改变你的编译器,你也会被困在64位平台上。

平台API

我们的程序需要编译,它们通常用于与运行的计算机进行交互:

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

您需要找到跨平台的便携式API,或者自己推送。检查下面列表中的一些库。

<强>库

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

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

好的图书馆涉及:

  • Apache(图书馆馆藏)
  • 升压
  • Qt(图示)
  • ICU(用于Unicode处理)

您需要审核的其他人......这需要时间。

我认为那里没有完美的答案。但由于无法实现完美的可移植性,因此需要确定您希望支持的编译器和平台。

对于平台,您应该从Windows和一种Linux风格开始。对于编译器,选择任意两个(如果你能负担得起的话,请选择Comeau)。

答案 6 :(得分:2)

一些指导原则:

  1. 保持代码和GUI的业务端分开。
  2. 避免使用编译器特定的拐杖(#pragma等)
  3. 使用传统的表达式,不会改变编译器/平台的行为,而不是可爱的位操作技巧。
  4. 如果它触及硬件,则它属于设备驱动程序。
  5. 使用数据类型标题,例如types.h(uint32_t等)。
  6. 使用操作系统抽象层,这样就不会直接调用操作系统调用。
  7. 有时您必须权衡效率和性能以获得可移植性。例如,如果您的代码需要从缓冲区中访问字段,则始终可以将压缩结构转换为缓冲区指针。但这非常不便携。因此,您需要使用使用偏移量计算的命名指针 - 有时使用边界对齐处理代码。不漂亮,但便携。幸运的是,你可以通过明智地使用类接口来隐藏很多东西。

    并非所有代码都需要以这种方式编写。如果您以非常模块化的方式设计应用程序,并且具有明确的责任范围,那么90-95%的代码可以轻松移植。然后在需要为新平台定制的非常本地化的区域中隔离5-10%。

答案 7 :(得分:2)

其他人之前说过,但这是我对此的看法:

1)你需要C ++吗?它不是编写可移植代码的最佳语言,因为它接近于裸机。 Java,Python,Perl,PHP或Javascript可能对您更好。

2)如果你需要C ++,不要试着写完全可移植的代码,无论如何这几乎是不可能的。相反,尽早决定要支持哪些平台。例如:Linux,MacOS X,Windows

3)确保连续测试所有选定平台上的代码。不要只是在Windows上构建,而是希望在“完成后”编译Linux版本。每天在所有平台上编译,并确保您继续测试它们是否存在问题。

答案 8 :(得分:1)

为了学习,尽量避免专注于一个实现的书籍。在某些情况下,介绍或早期章节将为您提供有关如何获取或使用语言实现的一些说明;如果它提到多个实现,你可能没问题。

获取与平台无关的参考书。 Stroustrup的 C ++编程语言是一个很好的参考,尽管对于初学者来说,尝试学习它并不是一本好书。不要依赖于给定实现的引用。例如,MSDN非常有用,但其主要关注点是如何使用Visual C ++编写Windows程序,而不是如何编写将在任何地方编译和运行的程序。

要编写任何真正有用的内容,您将不得不进入不可移植的代码。尝试养成将用户界面代码与其他所有内容分开的习惯,因为那时你的兼容性最低。您需要在平台之间更改的代码越少,代码的可移植性就越高。

答案 9 :(得分:1)

独立于操作系统的代码在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()函数而不需要求助于条件编译。

答案 10 :(得分:1)

如果可以的话,用至少两个不同的编译器编译所有代码。

答案 11 :(得分:0)

一个好主意是使用POSIX系统调用。这样您就不必处理创建线程或使用互斥锁和信号的不同方法。

问题是Windows并不完全符合POSIX标准,但有些库实现了某些POSIX功能,如下所示:[1]:http://sourceware.org/pthreads-win32/