以下是Bjarne Stroustrup的书“The C ++ Programming Language”的摘录:
第4.6节:
C ++的基本类型的某些方面,例如int的大小,是实现定义的(§C.2)。我指出了这些依赖关系,并经常建议避免它们或采取措施尽量减少它们的影响。你为什么要打扰?在各种系统上编程或使用各种编译器的人非常关心,因为如果他们不这样做,他们就不得不浪费时间寻找和修复模糊的错误。声称他们不关心可移植性的人通常会这样做,因为他们只使用一个系统,并且觉得他们能够承担“我的编译器实现的语言”的态度。这是一个狭隘而短视的观点。如果您的程序成功,可能会被移植,因此有人必须找到并修复与实现相关的功能相关的问题。此外,程序通常需要与同一系统的其他编译器一起编译,甚至您喜欢的编译器的未来版本可能会做一些与当前版本不同的东西。在编写程序时,了解并限制实现依赖性的影响要比在之后尝试解决混乱更容易。
限制依赖于实现的语言功能的影响相对容易。
我的问题是:如何限制依赖于实现的语言功能的影响?请提及依赖于实现的语言功能,然后展示如何限制其影响。
答案 0 :(得分:4)
嗯,提到的变量大小是一个众所周知的问题,常见的解决方法是提供具有良好定义大小的基本类型的typedeffed版本(通常在typedef名称中公布)。这样做是使用预处理器宏在不同平台上提供不同的代码可见性。 E.g:
#ifdef __WIN32__
typedef int int32;
typedef char char8;
//etc
#endif
#ifdef __MACOSX__
//different typedefs to produce same results
#endif
其他问题通常也以同样的方式解决(即使用预处理程序令牌执行条件编译)
答案 1 :(得分:4)
很少有想法:
不幸的是,您必须使用宏来避免某些特定于平台或编译器的问题。您可以查看Boost库的标题,看看它很容易变得很麻烦,例如查看文件:
整数类型在不同平台之间往往是混乱的,您必须定义自己的typedef或使用Boost cstdint.hpp
如果您决定使用任何库,请检查指定平台上是否支持该库
使用具有良好支持的库和明确记录的平台支持(例如Boost)
您可以通过严重依赖Qt这样的库来从一些C ++实现特定问题中抽象出来,Qt提供了类型和算法意义上的“替代”。他们还试图使C ++中的编码更具可移植性。它有用吗?我不确定。
并非一切都可以通过宏来完成。您的构建系统必须能够检测平台和某些库的存在。许多人建议autotools
进行项目配置,另一方面我推荐CMake
(相当不错的语言,不再是M4
)
如果你做一些低级干预(即reinterpret_cast
和朋友相似的事情(朋友在C ++上下文中是一个坏词)),字节顺序和对齐可能是一个问题。
为编译器引入了很多警告标志,对于gcc,我建议至少-Wall -Wextra
。但还有更多,请参阅编译器的文档或此question。
你必须注意实现定义和实现依赖的所有内容。如果你想要真相,只有真相,只需要真相,那就去ISO标准。
答案 2 :(得分:3)
最明显的实现依赖是整数类型的大小。有很多方法可以解决这个问题。最明显的方法是使用typedef创建各种大小的整数:
typedef signed short int16_t;
typedef unsigned short uint16_t;
这里的诀窍是选择一个约定并坚持下去。哪个约定是困难的部分:INT16,int16,int16_t,t_int16,Int16等.C99具有使用int16_t样式的stdint.h文件。如果您的编译器有此文件,请使用它。
同样,你应该对使用其他标准定义(如size_t,time_t等)感到迂腐。
另一个技巧是知道何时不使用这些typedef。用于索引数组的循环控制变量应该只使用原始int类型,因此编译将为您的处理器生成最佳代码。 for(int32_t i = 0; i< x; ++ i)可以在64位处理器上生成大量不必要的代码,就像在32位处理器上使用int16_t一样。
答案 3 :(得分:2)
一个好的解决方案是使用将typedeff'ed类型定义为必要的常用标题。
例如,包括sys / types.h是处理此问题的绝佳方法,就像使用可移植库一样。
答案 4 :(得分:2)
有两种方法:
第一种非常受欢迎,但是第二种,如果可能的话,通常会产生更清晰的代码。这包括:
最近的一个例子是编写可以为x86和x64平台编译的代码。这里的危险部分是指针和size_t大小 - 准备好它可以是4或8,具体取决于平台,当转换或差异指针时,不会转换为int,而是使用intptr_t和类似的typedef-ed类型。
答案 5 :(得分:1)
避免依赖特定数据大小的关键方法之一是阅读&将持久数据写为文本,而不是二进制。如果必须使用二进制数据,则所有读/写操作必须集中在一些方法和方法中,如此处所述的typedef使用。
您可以做的第二件事就是启用所有编译器警告。例如,在g ++中使用 -pedantic 标志会警告您存在许多潜在的可移植性问题。
答案 6 :(得分:0)
如果您担心可移植性,可以毫不费力地确定和处理类似int的大小。许多C ++编译器也支持类似int类型的C99功能:int8_t
,uint8_t
,int16_t
,uint32_t
等。如果你的本机不支持它们,那么你可以始终包含<cstdint>
或<sys/types.h>
,其中通常包含typedef
个<limits.h>
。 sizeof(char) < sizeof(short) <= sizeof(int) <= sizeof(long)
具有所有基本类型的这些定义。
标准仅保证类型的最小大小,您始终可以依赖它:char
。 short
必须至少为8位。 int
和long
必须至少为16位。 export "C++"
必须至少为32位。
可能是实现定义的其他内容包括ABI和名称修改方案(具体为{{1}}的行为),但除非您使用多个编译器,否则这通常不是问题。
答案 7 :(得分:0)
以下是Bjarne Stroustrup的书“C ++编程语言”的摘录:
第10.4.9节:
不对不同编译单元中非局部对象的构造顺序进行与实现无关的保证。例如:
// file1.c: Table tbl1; // file2.c: Table tbl2;
tbl1 是否在 tbl2 之前构建,反之亦然是与实现相关的。甚至不保证在每个特定实现中修复订单。动态链接,甚至编译过程中的微小变化都可以改变顺序。破坏的顺序与执行有关。
程序员可以通过实现实现通常用于本地静态对象的策略来确保正确初始化:第一次切换。例如:
class Zlib { static bool initialized; static void initialize() { /* initialize */ initialized = true; } public: // no constructor void f() { if (initialized == false) initialize(); // ... } // ... };
如果有许多功能需要测试第一次切换,这可能很乏味,但通常是可以管理的。此技术依赖于以下事实:没有构造函数的静态分配对象被初始化为 0 。真正困难的情况是第一个操作可能是时间关键的,因此测试和可能的初始化的开销可能很严重。在这种情况下,需要进一步的诡计(§21.5.2)。
简单对象的另一种方法是将其表示为函数(第9.4.1节):
int& obj() { static int x = 0; return x; } // initialized upon first use
首次开关不能处理所有可能的情况。例如,可以创建在构造期间彼此引用的对象。最好避免这样的例子。如果这些物品是必要的,则必须分阶段仔细构建。