1.数据的存储和排列
是的,在C语言中,整数类型通常以补码(two's complement)形式存储在内存中。这是因为补码表示法在处理有符号整数的加减运算上更为简便和高效。
基本类型所占字节数:
大端方式存储
就相当于我们平时的习惯,位权高的数写在最左端,例如:100 元。
内存地址 | 0 | 1 | 2 | 3 |
存储内容 | 数据的最高有效字节 | 数据的次高有效字节 | 数据的次低有效字节 | 数据的最低有效字节 |
以 0x12345678 为例子: | ||||
内存地址 | 0 | 1 | 2 | 3 |
存储内容 | 0x12 | 0x34 | 0x56 | 0x78 |
小端方式存储
位权低的数写在最左端,计算机内部存储数据最常用:
例如8086cpu的寄存器中所存数据:
该汇编语言是将1234H存入寄存器AX中在送进内存1000:0000处
可以看到1000:0000出存的是1234H的低位字节34H,1000:0001处存的是1234H的高位字节12H.
内存地址 | 0 | 1 | 2 | 3 |
存储内容 | 数据的最低最高有效字节 | 数据的次低有效字节 | 数据的次高有效字节 | 数据的最高有效字节 |
以 0x12345678 为例子: | ||||
内存地址 | 0 | 1 | 2 | 3 |
存储内容 | 0x78 | 0x56 | 0x34 | 0x12 |
边界对齐方式存储
字节对齐是一种计算机内存存储数据的方式。
在计算机系统中,为了提高内存访问效率,数据通常按照特定的规则在内存中进行存储,这就是字节对齐。
其基本规则通常是:结构体或类中的成员变量,其存储地址应该是其自身大小的整数倍。例如,一个 4 字节大小的整数类型变量,通常会存储在地址能被 4 整除的内存位置。
字节对齐的主要目的有两个:一是提高 CPU 访问内存的效率,因为大多数计算机体系结构在访问对齐的数据时效率更高;二是便于不同硬件平台和编译器之间的数据交换和移植。
假设一个结构体包含一个 char 类型(1 字节)和一个 int 类型(4 字节)。如果没有字节对齐,char 类型之后紧接着存储 int 类型,可能导致 int 类型的存储地址不是 4 的倍数,从而在访问 int 类型数据时降低效率。但如果进行字节对齐,char 类型之后可能会填充若干字节,使得 int 类型从能被 4 整除的地址开始存储。
假设我们有这样一个结构体:
struct MyStruct {int num; // 4 字节char ch; // 1 字节short sh; // 2 字节
};
在大多数常见的编译器中,存储方式通常如下:
内存地址 | 0 | 1 | 2 | 3 |
存储内容 | int num 的低字节 | int num 的次低字节 | int num 的次高字节 | int num 的高字节 |
内存地址 | 4 | 5 | 6 | 7 |
存储内容 | char ch | 填充字节 | short sh 的低字节 | short sh 的高字节 |
首先存储 int
类型的 num
,由于 int
通常是 4 字节对齐,所以 num
会从能被 4 整除的地址开始存储。
然后是 char
类型的 ch
,它占用 1 字节。但为了满足后面 short
类型的 2 字节对齐要求,可能会在 ch
后面填充 1 字节。
最后是 short
类型的 sh
,它从能被 2 整除的地址开始存储。
总的来说,字节对齐可能会导致结构体在内存中占用的空间比成员变量实际大小之和要大一些,这是为了提高内存访问效率。
2.有符号数和无符号数之间的转换
在C语言中,有符号数和无符号数之间的转换可以通过类型转换(type casting)实现。需要注意的是,这种转换会影响数值的解释。
有符号数转无符号数
将有符号数转换为无符号数时,二进制表示保持不变,但解释方式不同。
#include <stdio.h>int main(){int signed_num = -1;unsigned int unsigned_num = (unsigned int)signed_num; // 0xFFFFFFFFprintf("signed_num = %d, unsigned_num = %u\n", signed_num, unsigned_num);return 0;
}
输出结果:
有符号数转无符号数在内存中的解释:
变量名 | 内存地址 | 二进制表示 | 解释 |
---|---|---|---|
signed_num | 0x7fffeeaee10 | 11111111 11111111 11111111 11111111 | -1 (有符号数) |
unsigned_num | 0x7fffeeaee14 | 11111111 11111111 11111111 11111111 | 4294967295 (无符号数) |
可以看到二进制表示保持不变,只是改变了解释方式。
无符号数转有符号数
将无符号数转换为有符号数时,同样二进制表示保持不变,但解释方式不同。
#include <stdio.h>int main(){unsigned int unsigned_num = 4294967295; // 0xFFFFFFFFint signed_num = (int)unsigned_num; // -1printf("unsigned_num = %u, signed_num = %d\n", unsigned_num, signed_num);return 0;
}
输出结果:
无符号数转有符号数在内存中的解释:
变量名 | 内存地址 | 二进制表示 | 解释 |
---|---|---|---|
unsigned_num | 0x7fffeeaee18 | 11111111 11111111 11111111 11111111 | 4294967295 (无符号数) |
signed_num | 0x7fffeeaee1c | 11111111 11111111 11111111 11111111 | -1 (有符号数) |
可以看到二进制表示保持不变,只是改变了解释方式。
3.不同字长整数之间的转换
在C语言中,不同字长的整数之间的转换主要涉及位宽扩展和截断操作。
从小字长向大字长扩展
对于有符号数,可以通过符号扩展实现扩展;对于无符号数,可以通过零扩展实现扩展。
#include <stdio.h>int main(){char small_num = -1;int large_num = (int)small_num; // 符号扩展,小数到大数printf("small_num = %d, large_num = %d\n", small_num, large_num);unsigned char usmall_num = 255;unsigned int ularge_num = (unsigned int)usmall_num; // 零扩展printf("usmall_num = %u, ularge_num = %u\n", usmall_num, ularge_num);return 0;
}
输出结果:
从小字长向大字长扩展在内存中的解释:
变量名 | 内存地址 | 二进制表示 | 解释 |
---|---|---|---|
small_num | 0x7fffeeaee20 | 11111111 | -1 (8位有符号数) |
large_num | 0x7fffeeaee24 | 11111111 11111111 11111111 11111111 | -1 (32位有符号数) |
变量名 | 内存地址 | 二进制表示 | 解释 |
---|---|---|---|
usmall_num | 0x7fffeeaee28 | 11111111 | 255 (8位无符号数) |
ularge_num | 0x7fffeeaee2c | 00000000 00000000 00000000 11111111 | 255 (32位无符号数) |
对于有符号数,从小字长向大字长可以通过符号扩展实现扩展;
从大字长向小字长截断
当将大字长整数转换为小字长整数时,高位会被截断,只保留低位。
#include <stdio.h>int main(){int large_num = 257; // 0x00000101char small_num = (char)large_num; // 0x01printf("large_num = %d, small_num = %d\n", large_num, small_num);unsigned int ularge_num = 65537; // 0x00010001unsigned char usmall_num = (unsigned char)ularge_num; // 0x01printf("ularge_num = %u, usmall_num = %u\n", ularge_num, usmall_num);return 0;
}
输出结果:
大字长到小字长整数之间的转换在内存中的解释:
数据类型 | 变量名 | 值 | 内存地址示例 | 二进制表示 | 解释 |
int | large_num | 257 | 0x7010 | 00000000 00000000 00000001 00000001 | 257 |
char | small_num | 1 | 0x7000 | 00000001 | 1(截断) |
unsigned int | ularge_num | 65537 | 0x7014 | 00000000 00000001 00000000 00000001 | 65537 |
unsigned char | usmall_num | 1 | 0x7008 | 00000001 | 1(截断) |
可以看到 大字长到小字长整数之间的转换在是直接截去多余的高位,只保留对应得低位。