C编程注意32位机器和64位机器的差别及unsigned和signed

本文对比了64位与32位系统在CPU寻址能力、数据处理性能及编程实践上的区别,详细介绍了不同位数系统对内存支持、数据类型长度的影响,并提供了跨平台编程建议。

1.64bit CPU拥有更大的寻址能力,最大支持到16GB内存,而32bit只支持4G内存
2.64位CPU一次可提取64位数据,比32位提高了一倍,理论上性能会提升1倍。但这是建立在64bit操作系统,64bit软件的基础上的。

C/C++ 32位机器和64位机器 差异问题总结 跨平台 移植问题 语言编程需要注意的64位和32机器的区别

#include < stddef.h>
OS version:Red Hat Enterprise Linux Server release 5.3 (Tikanga) linux 2.6.18-128.el5 #1 SMP Wed Dec 17 11:41:38 EST 2008 x86_64 x86_64 x86_64 GNU/Linux
size_t本身一个作用就是避免考虑64还是3264位下Long和指针是64位的
size_tm_unNo;
sprintf(path,"%u",m_unNo); //这句在32位机器上正常 64位机器上会编译警告:“警告:格式 ‘%u’ 需要类型 ‘unsigned int’,但实参 4 的类型为 ‘size_t’”
%u 对应 unsigned int64位机器上还是32位,而size_t已经变成64位了。
char* 指针在64位下是64位
m_pMem = new char[nSize];
int off = (int)m_pMem%nAlign; //32位编译正常,在64位机器上编译报错:“ 错误:从 ‘char*’ 到 ‘int’ 的转换损失精度”
改为就可以达到兼容效果了int off = (uint64_t)m_pMem%nAlign; // 因为int64位下仍为32位,char×已经变位64位了。

一、数据类型特别是int相关的类型在不同位数机器的平台下长度不同。C99标准并不规定具体数据类型的长度大小,只规定级别。作下比较:

16位平台

char 1个字节8位
short 2个字节16位
int 2个字节16位
long 4个字节32位
指针 2个字节

32位平台

char 1个字节8位
short 2个字节16位
int 4个字节32位
long 4个字节
long long 8个字节
指针 4个字节

64位平台

char 1个字节
short 2个字节
int 4个字节
long 8个字节(区别)
long long 8个字节
指针 8个字节(区别)

二、编程注意事项

为了保证平台的通用性,程序中尽量不要使用long数据库型。可以使用固定大小的数据类型宏定义:

typedef signed char       int8_t
typedef short int             int16_t;
typedef int                      int32_t;
# if __WORDSIZE == 64
typedef long int              int64_t;
# else
__extension__
typedef long long int      int64_t;
#endif

三、使用int时也可以使用intptr_t来保证平台的通用性,它在不同的平台上编译时长度不同,但都是标准的平台长度,比如64位机器它的长度就是8字节,32位机器它的长度是4字节,定义如下:

#if __WORDSIZE == 64
typedef long int                intptr_t;
#else
typedef int                        intptr_t;
#endif

编程中要尽量使用sizeof来计算数据类型的大小
以上类型定义都有相应的无符号类型。
另外还有ssize_t和size_t分别是unsigned和signed size of computer word size。它们也是表示计算机的字长,在32位机器上是int型,在64位机器上long型,从某种意义上来说它们等同于intptr_t和uintptr_t。它们在stddef.h里面定义。需要注意的是socket的accept函数在有些操作系统上使用size_t是不正确的,因为accept接收的int*类型,而size_t可能是long int 类型。后来BSD使用sock_t来替代它。

一、C

1.几条规则

(1)char类型一般是8bit,但ANSI C里没有硬性规定其长度,某些嵌入式编译器可能是16bit

(2)short和long类型的长度不相同

(3)int类型通常同具体机器的物理字长相同

(4)short通常是16bits, int通常是16bits or 32bits每种编译器可以根据硬件的不同自由确定, 但是short和int必须最少是16bits, 而long类型必须最少是32bits, 并且short必须比int和long类型要短。

2.32位机上类型长度

size of char: 1

size of int: 4

size of long:4

size of float:4

size of long long:8

size of double:8

size of long double:12

size of char * :4

3.64位机上类型长度

size of char:1

size of int:4

size of long :8

size of float :4

size of long long:8

size of double:8

size of long double:16

size of char * :8

4.16位机类型长度

char: 1

int: 2

long:4

unsigned int:2

unsigned short:2

unsigned long:4

float:4

size of char * :4

二、C++

1.字节和字长

字节,八位就是一个字节,是固定概念。字长是指计算机一次能处理的二进制数据的长度,是一个非固定的概念。例如,8位计算机的字长为8,即一个字节, 32位计算机的字长位32,即4个字节,同理,64位计算机的字长为64,即8字节。

2.char类型始终是一个字节长,即8位。

3.int、short int和long int

通常int为一个字长,short为半个字长,long为一个或2个字长(在32位机器中为一个字长)。

4.浮点型float、双精度double、和长双精度long double

典型情况下,float 为一个字,double是两个字,long double为三个或四个字。

32位机指针为什么是4个字节 64位与32位机的区别

int类型比较特殊,具体的字节数同机器字长和编译器有关。如果要保证移植性,尽量用__int16 __int32 __int64吧,或者自己typedef int INT32一下。C、C++标准中只规定了某种类型的最小字节数(防止溢出)。

通常的64位技术是相对32位而言的,这个位数指的是CPU GPRs(General-Purpose register通用寄存器)的数据宽度为64位,而32位的处理器的通用寄存器的数据宽度为32位,64位指令集就是运行64位数据的指令,也就是说一次可以运行64bit的数据。

通用寄存器:可用于传送和暂存数据,也可以参与算术逻辑运算,并保存运算结果。通用寄存器的长度取决于机器字长。

字长:字长是CPU的主要技术指标之一,指的是CPU一次能并行处理的二进制的位数,字长是8的整倍数,通常的PC机的字长为16位,32位,64位。一台16位字长的PC机可以直接处理2^16(65536)之内的数字,对于超过此范围的数字需要分解的方法来处理。32位机比16位机优越的原因之一就在于它在一次操作中能处理的数字大,32位机字长的PC机能直接处理的数字为2^32(40亿),能处理的数字越大,则操作的次数就越少,从而系统的效率就越高。

字长与寻址空间:处理器字长是指处理机能同时处理的位数,处理器的字长越大,则说明它的运算能力越强。

处理的寻址范围:要看处理器的地址总线的位数,而不是它的字长。!!!!!如Intel P4处理器字长为32位,地址总线也是32位。8086的数据总线为16为,地址总线为20位(则可寻址的内存空间为2^20=1MB)。新兴的64位处理器的数据总线为64位,地址总线大部分是32位。再看地址总线与寻址范围的关系,存储单元是以Byte为单位,N根地址总线能够访问2^N个存储单元,于是有32为地址总线可访问2^32个存储单元,即4GB。

   所以指针为了正确指示内存中的地址,必须按照地址总线的宽度进行变量的存储,因此虽说64位CPU的数据宽度为64位而其地址总线一般不为64位(能访问的内存空间大的惊人,暂时估计应该还做不到),但是一般能超过32位,因此指针的长度大于4个字节(32位),所以64位机的指针字节为64位即8个字节,而32位机的地址总线一般为4个字节,即支持4GB的内存,则其指针的宽度为4个字节。

一)64位系统和32位有什么区别?

1、64bit CPU拥有更大的寻址能力,最大支持到16GB内存,而32bit只支持4G内存
2、64位CPU一次可提取64位数据,比32位提高了一倍,理论上性能会提升1倍。但这是建立在64bit操作系统,64bit软件的基础上的。

什么是64位处理器?
之所以叫做“64位处理器”,是因为电脑内部都是实行2进制运算,处理器(CPU)一次处理数据的能力也是2的倍数。8位处理器、16位处理器、32位处理器和64位处理器,其计数都是2的倍数。一次处理的数据越大,该电脑处理信息的能力越来越大;因此64位处理在先天就比32位处理器具有快速的能力。那为什么不用更高级的128位处理器呢?因为位数越高,处理器芯片的设计也就越复杂,目前的技术水平暂时无法制造这么复杂的芯片。

64位处理器之失
※硬件———缺乏驱动程序,很多现有硬件无法使用
※软件———操作系统不是问题,但是软件出现不兼容难题

64位处理器之得
※硬件———更快的执行速度,更大的内存管理
※软件———最新的尖端软件首先出现在64位平台

(二)数据类型对应字节数

程序运行平台
不同的平台上对不同数据类型分配的字节数是不同的。
个人对平台的理解是CPU+OS+Compiler,是因为:
1、64位机器也可以装32位系统(x64装XP);
2、32位机器上可以有16/32位的编译器(XP上有tc是16位的,其他常见的是32位的);
3、即使是32位的编译器也可以弄出64位的integer来(int64)。
以上这些是基于常见的wintel平台,加上我们可能很少机会接触的其它平台(其它的CPU和OS),所以个人认为所谓平台的概念是三者的组合。
虽然三者的长度可以不一样,但显然相互配合(即长度相等,32位的CPU+32位的OS+32位的Compiler)发挥的能量最大。
理论上来讲 我觉得数据类型的字节数应该是由CPU决定的,但是实际上主要由编译器决定(占多少位由编译器在编译期间说了算)。
常用数据类型对应字节数
可用如sizeof(char),sizeof(char*)等得出
32位编译器:
char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 4个字节
long long: 8个字节
unsigned long: 4个字节
64位编译器:
char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 8个字节
long long: 8个字节
unsigned long: 8个字节

转载自:
http://blog.youkuaiyun.com/xad707348125/article/details/39450147

最后补充unsigned类型和signed的区别:

 整型的每一种都分为:无符号(unsigned)和有符号(signed)两种类型(float和double总是带符号的),在默认情况下声明的整型变量都是有符号的类型(char有点特别),如果需声明无符号类型的话就需要在类型前加上unsigned。无符号版本和有符号版本的区别就是无符号类型能保存2倍于有符号类型的正整数数据,比如16位系统中一个short能存储的数据的范围为-32768~32767,而unsigned能存储的数据范围则是0~65535。由于在计算机中,整数是以补码形式存放的。根据最高位的不同,如果是1,有符号数的话就是负数;如果是无符号数,则都解释为正数。另外,unsigned若省略后一个关键字,大多数编译器都会认为是unsigned int。

unsigned正数表示范围是signed的2倍

类型

说明
tinyint非常小的整数
smallint较小整数
mediumint中等大小整数
int标准整数
bigint较大整数
float单精度浮点数
double双精度浮点数
decimal一个串的浮点数

每种数值类型的名称和取值范围如下表所示
类型说明 —————————— 取值范围
tinyint[(m)]
有符号值:-128 到127(- 27 到27 - 1)
无符号值:0到255(0 到28 - 1)1个字节
smallint[(m)]
有符号值:-32768 到32767(- 215 到215 - 1)
无符号值:0到65535(0 到21 6 - 1)2个字节
mediumint[(m)]
有符号值:-8388608 到8388607(- 22 3 到22 3 - 1 )
无符号值:0到16777215(0 到22 4 - 1)3个字节
int[(m)]
有符号值:-2147683648 到2147683647(- 231 到231- 1)
无符号值:0到4294967295(0 到232 - 1)4个字节
bigint[(m)]
有符号值:-9223372036854775808 到9223373036854775807(- 263到263-1)
无符号值:0到18446744073709551615(0到264 – 1) 8个字节
float[(m, d)]
最小非零值:±1.175494351e - 38
double[(m,d)]
最小非零值:±2.2250738585072014e - 308
decimal (m, d)
可变;其值的范围依赖于m 和d

<think>我们正在讨论的是在内存拷贝操作中,为什么使用unsigned long而不是long作为单长度。根据之前的讨论,核心原因在于性能优化,但这里需要更具体地解释为什么选择unsigned long而不是long。 关键点: 1. 避免符号扩展问题:使用无符号类型可以避免在拷贝过程中可能出现的符号扩展问题。因为内存地址内容都是非负的,使用无符号类型更自然。 2. 地址空间覆盖:在32系统中,unsigned long是32,在64系统中是64,这通常与指针的数相同,因此可以一次性处理一个指针大小的数据。 3. 标准库实践:参考引用[2]中提到的Linux内核实践,他们使用unsigned long来代表内存地址(代替指针)进行内存管理,因为unsigned long的数与CPU可寻址的内存地址数相同。 具体原因分析: 一、避免符号引起的意外行为 当使用有符号类型(如long)进行内存操作时,如果遇到最高为1的数据(即当数据被解释为有符号数时是负数),在进行某些操作(如右移)时,有符号数会进行符号扩展(即高补1),而无符号数则是补0。在内存拷贝中,我们希望原样复制比特,因此使用无符号类型可以避免这种符号扩展带来的问题。 例如: unsigned long a = 0x80000000; // 假设32系统,最高为1 long b = 0x80000000; // 同样值,但作为有符号数,它是负数 // 右移操作 a >> 1; // 得到 0x40000000 (高补0) b >> 1; // 得到 0xC0000000 (高补1,因为符号是1) 在内存拷贝中,我们不希望改变原始数据的比特模式,所以使用无符号类型。 二、地址空间与无符号数的对应关系 内存地址是自然数(从0到最大地址),因此使用无符号整数来表示地址空间是合理的。引用[1]中提到:“指针整数(这里主要是unsigned long)本身是八竿子打不着的,但是它们之间的一个有趣联系是:” 实际上,在需要将地址当作整数进行运算时(比如偏移计算),使用unsigned long可以确保整个地址空间都被覆盖,而不会因为符号问题导致一半的地址空间无法表示(例如,有符号long在32系统上只能表示0到2^31-1,而2^31到2^32-1的地址会被解释为负数,从而可能在某些运算中出现问题)。 三、与指针大小匹配 在Linux内核中,常常使用unsigned long来代替指针,因为unsigned long的数与指针的数相同(引用[2])。因此,使用unsigned long作为拷贝单,可以确保一次拷贝一个机器字(word),这是处理器处理效率最高的数据大小。 四、标准库函数的参考 引用[3]中提到的memcpy函数,其原型是: void *memcpy(void *dest, const void *src, size_t n); 其中n是字节数,但实际实现中,为了提高效率,内部通常会将字节数按字长对齐,并使用字长(如unsigned long)为单进行拷贝。例如,在glibc的实现中,当拷贝长度足够大时,会使用更长的数据类型(如long)来一次拷贝多个字节。 五、无符号数的模运算特性 在内存操作中,地址运算经常需要回绕(wrap around),而无符号数的模运算特性(当超过最大值时自动回绕)在某些情况下是符合内存地址运算的预期的(虽然通常我们不会让地址回绕,但在一些底层操作中,无符号数的溢出是定义良好的行为,而有符号数的溢出是未定义行为)。 因此,总结来说,在内存拷贝中使用unsigned long而不是long,主要是为了避免符号扩展问题,确保覆盖整个地址空间,与指针大小匹配,以及利用无符号数在溢出时的定义良好行为。 注意:在C语言中,使用unsigned long进行内存拷贝时,需要注意内存对齐问题,否则可能导致性能下降甚至硬件异常(在严格对齐的架构上)。 引用: [^1]: 宋宝华:为什么Linux内核常常用unsigned long来代替指针。 [^2]: 为什么Linux内核常用unsigned long来代替指针。 [^3]: memcpy()函数是直接操作内存空间的内存拷贝函数。 相关问题: 1. 在内存拷贝中,除了使用unsigned long,还有哪些数据类型可以用于高效拷贝? 2. 如何确保内存拷贝操作中的内存对齐? 3. 在非对齐内存访问的情况下,使用unsigned long进行拷贝会有什么问题? 4. 为什么有符号整数的溢出是未定义行为,而无符号整数的溢出是定义良好的?</think>在内存拷贝操作中使用 `unsigned long` 而非 `long` 作为单长度,主要基于以下关键原因,这些原因与处理器架构、内存寻址特性编程安全性密切相关: ### 一、地址空间的完整覆盖(核心原因) 1. **内存地址的本质** 内存地址是**非负整数**,从 `0x00000000` 到最大可寻址地址(如 64 系统的 `0xFFFFFFFFFFFFFFFF`)。`unsigned long` 的数值范围: - 32 系统:$[0, 2^{32}-1]$(覆盖 4GB 地址空间) - 64 系统:$[0, 2^{64}-1]$(覆盖 16EB 地址空间) ```c // 正确覆盖整个地址空间 unsigned long addr = 0xFFFFFFFF; // 合法(无符号) // 错误:有符号long会溢出 long addr = 0xFFFFFFFF; // 32系统上变成-1(未定义行为) ``` 2. **Linux 内核实践** 如引用[1][2]所述,Linux 内核常用 `unsigned long` 代替指针: ```c // 内核内存分配函数(mm/page_alloc.c) unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order); ``` 因为 `unsigned long` 的数始终等于 CPU 的**地址总线宽度**,确保能表示任何有效内存地址[^1][^2]。 --- ### 二、避免符号扩展问题 1. **操作的确定性** 内存拷贝需要精确的级复制,`unsigned long` 避免符号干扰: ```c unsigned long a = 0x80000000; // 二进制: 1000...0000 long b = 0x80000000; // 32系统上为负数 a >> 1; // 结果: 0x40000000(高补0) b >> 1; // 结果: 0xC0000000(高补1,符号扩展) ``` 内存拷贝要求**原样复制比特**,符号扩展会破坏数据完整性。 2. **地址运算的安全性** 指针运算依赖模 $2^N$ 回绕行为: ```c unsigned long max_addr = ULONG_MAX; max_addr + 1; // 安全回绕到0(符合地址空间特性) long max_signed = LONG_MAX; max_signed + 1; // 有符号溢出是未定义行为(UB) ``` --- ### 三、硬件兼容性优势 1. **与寄存器宽度匹配** | 系统架构 | 通用寄存器宽度 | `unsigned long` 宽度 | |----------|----------------|----------------------| | x86-32 | 32-bit | 32-bit | | ARM64 | 64-bit | 64-bit | | RISC-V64 | 64-bit | 64-bit | 处理器对**无符号整数的操作**有直接硬件支持,效率最高。 2. **避免意外符号扩展指令** 对比汇编输出: ```asm ; unsigned long 拷贝 (x86) mov rax, [rsi] ; 单指令加载64 ; long 拷贝 (可能插入符号扩展) movsxd rax, [rsi] ; 需额外处理符号 ``` --- ### 四、实际场景验证 1. **内存懒加载与写时拷贝** 如引用[4]所示的内存管理机制中,内核使用 `unsigned long` 跟踪物理页帧: ```c // 页表项结构 (arch/x86/include/asm/pgtable_types.h) typedef struct { unsigned long pte; } pte_t; ``` 因为页帧号(PFN)必须是非负整数[^4]。 2. **标准库实现参考** `memcpy` 的优化实现通常用 `unsigned long` 加速: ```c void* memcpy(void* dest, const void* src, size_t n) { unsigned long* d = dest; const unsigned long* s = src; // 以unsigned long为单批量拷贝 for (size_t i = 0; i < n/sizeof(long); i++) d[i] = s[i]; // 处理剩余字节... return dest; } ``` --- ### 五、使用 long 的风险 1. **地址截断风险** 在 64 系统上: ```c void* ptr = malloc(1GB); long addr = (long)ptr; // 可能丢失高32! ``` `long` 在 Windows 64 32 ,而 `unsigned long` 在 Linux 64 64 。 2. **标准兼容性问题** C 标准规定: > `uintptr_t` (C99) 是匹配指针宽度的无符号整数类型 > `unsigned long` 在多数系统等同于 `uintptr_t` --- ### 总结 使用 `unsigned long` 的核心优势在于: 1. **精准匹配地址空间**:无符号特性覆盖 $[0, 2^N-1]$ 完整范围 2. **避免符号污染**:确保操作结果确定且安全 3. **硬件友好**:直接对应处理器的无符号运算指令 4. **跨平台一致性**:Linux/Unix 系统的通用实践[^1][^2] 在内存拷贝等底层操作中,`unsigned long` 提供了最接近硬件特性的安全抽象层,而 `long` 的符号特性会引入不可预测的行为风险。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值