文章目录
一、数据在内存中的存储 1、基本数据类型存储 2、数组存储 3、结构体存储 1、基本存储规则 2、举例说明 3、查看结构体大小和成员偏移量的方法
二、大小端字节序 三、字节序的判断
一、数据在内存中的存储
1、基本数据类型存储
整型 :如int类型,通常在32位系统中占4个字节,在内存中以二进制补码的形式存储。例如,整数10的二进制表示为00000000 00000000 00000000 00001010,以小端字节序存储时,在内存中的顺序是0A 00 00 00;以大端字节序存储时,顺序是00 00 00 0A。浮点型 :单精度float类型一般占4个字节,双精度double类型占8个字节。浮点数在内存中的存储遵循IEEE 754标准,以符号位、指数位和尾数位的形式存储。例如,单精度浮点数3.14在内存中的存储形式与整数完全不同。字符型 :char类型通常占1个字节,用来存储单个字符的ASCII码值。例如,字符'A'的ASCII码值是65,在内存中存储为41(十六进制)。
int main ( )
{
printf ( "%zd\n" , sizeof ( int ) ) ;
printf ( "%zd\n" , sizeof ( char ) ) ;
printf ( "%zd\n" , sizeof ( float ) ) ;
return 0 ;
}
2、数组存储
数组中的元素在内存中是连续存储的。例如,int arr[5] = {1, 2, 3, 4, 5};,数组arr的5个元素在内存中依次排列,假设数组首地址为0x1000,那么arr[0]存储在0x1000处,arr[1]存储在0x1004处,以此类推,每个元素之间的偏移量为sizeof(int)。
3、结构体存储
1、基本存储规则
结构体的成员在内存中是按照定义的顺序依次存储的。每个成员的存储地址相对于结构体首地址有一定的偏移量。例如,对于结构体struct Example { int a; char b; };,首先存储int型成员a,然后存储char型成员b。 编译器可能会在结构体成员之间插入填充字节(Padding),这是为了满足成员的对齐要求。对齐要求通常是为了提高内存访问的效率,因为大多数计算机硬件在访问内存时,对于按照一定字节对齐的数据访问速度更快。例如,在32位系统中,int类型通常要求4字节对齐,double类型要求8字节对齐等。
2、举例说明
例1:简单结构体
考虑结构体struct Simple { char c; int i; };。假设char类型占1个字节,int类型占4个字节。 首先存储c,其地址假设为结构体首地址0x0000,占1个字节,存储范围是0x0000。 由于int类型要求4字节对齐,在c和i之间会插入3个填充字节。i的存储起始地址为0x0004,占4个字节,存储范围是0x0004 - 0x0007。所以整个结构体Simple占8个字节。 例2:包含数组的结构体
定义结构体struct ArrayStruct { int a; char arr[3]; int b; };。 首先存储a,假设其地址为0x0000,占4个字节,存储范围是0x0000 - 0x0003。 接着存储arr数组,其起始地址为0x0004,因为char数组本身没有对齐要求,且前面a已经保证了4字节对齐。arr占3个字节,存储范围是0x0004 - 0x0006。 对于b,由于int类型要求4字节对齐,所以在arr和b之间会插入1个填充字节。b的存储起始地址为0x0008,占4个字节,存储范围是0x0008 - 0x000B。整个结构体ArrayStruct占12个字节。 例3:嵌套结构体
定义结构体struct Inner { char c; };和struct Outer { int a; struct Inner in; char b; };。 首先存储Outer结构体中的a,假设其地址为0x0000,占4个字节,存储范围是0x0000 - 0x0003。 接着存储Inner结构体中的c,由于Inner结构体是嵌套在Outer结构体中的,c的存储起始地址为0x0004,占1个字节,存储范围是0x0004。 对于Outer结构体中的b,因为char类型前面已经满足了4字节对齐(由于a的存储),所以b的存储起始地址为0x0005,占1个字节,存储范围是0x0005。整个Outer结构体占8个字节。
3、查看结构体大小和成员偏移量的方法
在C语言中,可以使用sizeof运算符来查看结构体的大小。例如,对于上述struct Simple结构体,可以通过printf("%d", sizeof(struct Simple));来输出结构体的大小。 有些编译器提供了扩展来查看结构体成员的偏移量,如在GCC中,可以使用__attribute__((packed))来取消结构体的对齐填充,使得结构体按照紧密排列的方式存储,这样可以更清楚地看到成员的原始偏移量。不过这种方式可能会影响内存访问效率,一般用于特殊的需求,如数据存储格式有严格要求的网络协议数据包的构建等。
二、大小端字节序
概念 :
大端字节序(Big-Endian) :也叫大端序或大字节序,数据的高位字节存于低地址,低位字节存于高地址。例如,对于整数0x12345678,高位字节0x12存于内存低地址,接着依次是0x34、0x56、0x78存于更高地址,就像按从左到右(高位在前)的顺序存储。小端字节序(Little-Endian) :又称小端序或小字节序,与大端字节序相反,数据的低位字节存于低地址,高位字节存于高地址。对于0x12345678,在小端字节序下,0x78存于内存低地址,接着是0x56、0x34、0x12存于更高地址,如同从右到左(低位在前)存储。 影响 :不同的字节序在多字节数据的存储和传输中会产生影响。在网络编程中,通常规定使用大端字节序进行数据传输,以保证不同主机之间数据的一致性。如果两台主机的字节序不同,在进行数据通信时就需要进行字节序的转换。
三、字节序的判断
利用指针类型转换判断 :通过将一个多字节数据的地址转换为char *类型指针,然后访问该指针指向的字节,根据第一个字节的值来判断字节序。例如:
# include <stdio.h>
int main ( ) {
int num = 1 ;
char * ptr = ( char * ) & num;
if ( * ptr == 1 ) {
printf ( "小端字节序\n" ) ;
} else {
printf ( "大端字节序\n" ) ;
}
return 0 ;
}
利用联合体判断 :联合体的所有成员共用同一块内存空间,可以利用这一特性来判断字节序。例如:
# include <stdio.h>
union EndianTest {
int num;
char bytes[ 4 ] ;
} ;
int main ( ) {
union EndianTest test;
test. num = 1 ;
if ( test. bytes[ 0 ] == 1 ) {
printf ( "小端字节序\n" ) ;
} else {
printf ( "大端字节序\n" ) ;
}
return 0 ;
}