文章目录
一、数据在内存中的存储 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 ;
}