本篇主要内容如下:
1.指针
2.内存对齐
3.sizeof
注意:以下都是基于32位系统。编译器为MinGw,GCC编译器的win版。
1.指针
(1)
首先要知道,指针里面的值是一个地址,根据这个地址就能找到想要访问的值。
例如:int a = 5;
char *p = &a。
p就是指向a,存的是a的地址。
简单来说就是地址。
当然你也可以直接给p赋值,此时右值就是一个地址。要确保该地址是个能够访问的,否则程序会出现错误崩溃。
(2)任何指针占用的字节数都是机器字长,在32位下就是4。即sizeof (p) = 4;
(3)关于指针的运算,p + 1, p++, p--, p - 1类似于这样的。p + 1相当于将p后移N个字节,N是由p指向的类型决定的。
比如:p是指向short类型,p + 1就是往后移了2字节。
指针的赋值,判断指针是否相等。说白了,拿来拿去的都是地址。
(4)类型转换。当将不同类型的指针相互赋值时,存在类型转换,也就是说会有截断的问题。其实跟其他类型转换都是一样的。
比如:
short a = 0xff00;
char *p1;
short *p2 = &a;
p1 = (char *)p2;
*p1 = ?
short 是2字节,char是1字节,当把p2赋给p1后,也就是说p1,p2是指向同一块内存。p1指向char,所以只取出1字节的数据作为结果。
所以*p1 = 0;在这里不考虑大端小端的问题。默认为小端。若为大端,结果是0xff。
简单解释下大端小端:小端就是高字节在高地址,低字节在低地址。大端是高字节在低地址,低字节在高地址。
一个判断大小端的方法:
union test
{
char a;
short b;
}
令b = 1,看a的值。若a为1,则为小端,为0,则为大端。
2.字节对齐
机器对内存的访问是按照字长来的,一次访问一个字长,32位下,就是4个字节。若对变量的访问地址是4的倍数,则访问只需要一次,否则要访
问2次,将结果拼接起来,降低了效率。在编译的时候,编译器会作对齐处理。
结构体中字节对齐规则:
对于基本数据类型,按其sizeof大小和指定对齐字节数中小的那个来进行对齐。未指定情况下,就是按自身类型的值
来。
比如指定对齐字节数为4,有个short类型变量,因为2 < 4, 所以是
按2字节对
齐。在未指定时,按sizeof(short)即2来对齐,称为按自然边界对齐。
对于结构体类型,按该结构体中基本类型的最大值和指定对齐字节数中小的那个来进行对齐。
最后总的字节数要是最大类型与指定字节数中小的那个的倍数。
比如:
struct t1
{
char a;
short b;
int c;
};
该结构体未指定对齐数,则按自然边界对齐方式来处理。a按1字节,b按2字节,c按4字节。
sizeof (struct t1) = 8, 在a后面填充了一字节。
struct t2
{
char a;
int b;
struct t1 t; // (以其基本数据类型中最大的来对齐,在此为int)
};
sizeof (struct t2) = 12;a后面会填充3字节,b要以4字节对齐。嵌套的struct t1以4字节对齐。
(1)自己指定对齐字节数
#pragma pack(n)
n为2的幂,并且超过默认的对其数就无效了。还是以默认的来。
若#pragma pack(1),则是以1字节对齐,结构紧凑,不会有填充的情况。sizeof (struct t1) = 1 + 2 + 4;
若#pragma pack(2),sizeof (struct t1) = 2 + 2 + 4 = 8; sizeof (struct t2) = 2 + 4 + 8
#pragma pack(),取消以n对齐。
例子:
指定4字节对齐。
#pragma pack(4)
struct A
{
char a;
short b;
char c;
};
#pragma pack()
sizeof (struct A) = ?;
计算:b为2字节,小于4,按2字节对齐,所以a后面填充一字节。c以1字节对齐,不需填充。此时2+2+1=5,但是总的字节数要是2个倍数,c后面还需填充1字节,所以为6。
当#pragma pack(1),则是按实际的大小,不填充任何字节。此时,sizeof(struct A) = 4;
(2)还一种方式是__attribute__((packed)),即以最小的对齐方式,以1字节对齐。
struct A
{
char a;
short b;
char c;
}__attribute__((packed));
结果同
#pragma pack(1);
__attribute__((packed))可作用于某个成员变量,让该变量以1字节对齐。
struct A
{
char a;
short b
__attribute__((packed))
;
char c;
};
结果是1+2+1=4;
(3)__attribute__((aligned(n)))是指让结构体的地址以n字节对齐,其大小也是n的倍数。与其内部成员变量的偏移对齐无关,只是声明该属性后,该类型的变量的地址会
会
以n字节对齐。
同样,该属性也可作用于基本类型变量, 即让该变量的地址在n的边界上。
struct A
{
char a;
short b
;
char c;
}
__attribute__((aligned(8)));
本来不加该属性时,sizeof的值是6,现在该结构体要以8字节对齐,则sizeof为8。是为了在连续存储空间存储多个结构体时,后面的结构体也是对齐的。
若
__attribute__((aligned))不加参数,则是让编译器采用最有益的方式,以达到最大的效率。
#pragma pack与__attribute__((aligned(n)))混用。
#pragma pack(2)
struct A
{
char a;
int b
;
char c[3];
}
__attribute__((aligned(8)));
#pragma pack()
记住
__attribute__((aligned(8)))并不影响成员变量在内存中的偏移量,偏移量是按#pragma pack来。在偏移量都确定好了之后,只是在最后填充字节来达到8字节
对齐。
结果是16。原本成员变量的偏移量对齐的结果是2+4+3,加上c后面填充的1字节,为10。现在因为是以8字节对齐,所以必须是8的倍数。
(4)__declspec(align(n)) n和自身值相比,取其大的对齐,总的字节数为最大基本数据类型和n中大的那个数的倍数;与#pragma pack相反。
3.sizeof
这里具体不说每种类型的大小,因为不同平台不同编译器下的结果是不同的,试下就知道。主要想说的是sizeof并不会对表达式进行计算,主要是取表达式或变量的类型。
例如:
int a = 2;
sizeof (++a);
sizeof(a+1) = sizeof(int);
之后a的值是多少? 答案应该是2,因为++a不会进行计算。