注:我知道有这篇文章: 如何编写既适用于32位又适用于64位的代码? 但我认为它太笼统了,没有好的示例,对像我这样的新手来说不是很友好。像32位存储单元是什么之类的概念,我希望能更详细地解释一下(没有恶意^^)。
sizeof()
(除非它们被制定为某个确切的大小),无论是显式还是隐式(这也包括可能的结构对齐)。指针只是其中的一部分,这也可能意味着您不应尝试依赖于能够在不相关的指针类型和/或整数之间进行转换,除非它们是专门用于此目的(例如intptr_t
)。同样,您需要注意写入磁盘的内容,在那里您也永远不应该依赖于例如内置类型的大小,在各处都相同。每当您必须使用明确定义大小的类型(例如uint32_t
)时,请小心处理(因为外部数据格式等原因)。int copy_of_pointer = ptr;
- 如果int是32位类型,则此代码将在64位机器上出现问题,因为只有部分指针将被存储。
只要指针仅存储在指针类型中,那么就没有问题。int main()
{
std::cout << sizeof(void*) << std::endl;
return 0;
}
sizeof(void*)
的结果是实现定义的。然而,可以编写包含实现定义行为但被解决为良好定义的程序。int main()
{
int size = sizeof(void*);
if (size != 4) {
size = 4;
}
std::cout << size << std::endl;
return 0;
}
4
。这是一个愚蠢的例子,因为我们本可以只做int size = 4;
,但在编写平台无关代码时确实存在一些情况。不要假设基本类型的大小除了C++标准规定的之外还有其他。也就是说,一个char
至少为8位,short
和int
至少为16位,依此类推。
不要试图进行指针操作(在指针类型之间转换或在整数类型中存储指针)。
不要使用unsigned char*
读取非char
对象的值表示(用于序列化或相关任务)。
避免使用reinterpret_cast
。
在进行可能超出或低于范围的操作时要小心。在执行位移操作时要仔细思考。
在指针类型上进行算术运算时要小心。
不要使用void*
。
最后是ABI(应用程序二进制接口)。64位和32位环境之间的差异通常比指针大小更大... 目前,存在两个主要的“分支”64位环境,即IL32P64(Win64使用的内容 - int和long是int32_t,只有uintptr_t/void*是uint64_t,以大小整数为单位)和LP64(UN*X使用的内容 - int是int32_t,long是int64_t和uintptr_t/void*是uint64_t),但也有不同对齐规则的“细分” - 一些环境假定长,浮点或双精度在它们各自的大小上对齐,而其他环境则假定它们在四字节的倍数上对齐。在32位Linux中,它们都对齐到四个字节,在64位Linux中,float对齐到四个字节,long和double对齐到八字节的倍数。 这些规则的结果是,在许多情况下,即使数据类型声明完全相同,32位和64位环境中的sizeof(struct { ...})和结构/类成员的偏移量也会不同。 除了影响数组/向量分配之外,这些问题还影响数据的输入/输出,例如通过文件 - 如果32位应用程序将struct { char a; int b; char c, long d; double e }写入文件,然后重新编译为64位应用程序读取,则结果将不会如预期。 上述示例仅涉及语言基元(char,int,long等),但当然会影响各种平台相关/运行时库数据类型,无论是size_t,off_t,time_t,HANDLE,还是任何非平凡的struct/union/class...因此,这里的错误空间很大,
而且还有一些更低级别的差异,例如在手动优化汇编过程中出现,32位和64位有不同的寄存器数量和参数传递规则,所有这些都会极大地影响这些优化的表现,很可能需要重写或增强某些在32位模式下表现最佳的SSE2代码,在64位模式下表现最佳。此外,32位和64位的代码设计约束也非常不同,特别是关于内存分配/管理方面;对于“尽可能多地利用32位内存”的应用程序来说,它们将具有关于如何/何时分配/释放内存、使用内存映射文件、内部缓存等复杂逻辑,这些大多数在64位中都是不利的,因为你可以“简单地”利用可用的大地址空间。这样的应用程序可能可以成功重新编译为64位,但在那里的表现却比一些“古老简单的废弃版本”差。