语法
结构体对齐
三条规则
- 结构体中元素是按照定义顺序一个一个放到内存中去的,但
并不是紧密排列的
。从结构体存储的首地址开始
,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分
,因此元素放置的位置一定会在自己宽度的整数倍上开始
(以结构体变量首地址为0计算)。 - 在经过第一原则分析后,检查计算出的存储单元是否为所有元素中
最宽的元素的长度的整数倍
,是,则结束;若不是,则补齐为它的整数倍。 - 结构体成员为结构体对象时需要着重理解
分别举例
1. 自己的大小来划分
2. 宽的元素的长度的整数倍
#include <iostream>
using namespace std;
struct X {
char a;
int b;
double c;
}s1;
struct Y {
char a;
double b;
int c;
}s2;
void print1() {
cout << sizeof(s1) << " ";
cout << sizeof(s1.a) << " ";
cout << sizeof(s1.b) << " ";
cout << sizeof(s1.c) << endl;
}
void print2() {
cout << sizeof(s2) << " ";
cout << sizeof(s2.a) << " ";
cout << sizeof(s2.b) << " ";
cout << sizeof(s2.c) << endl;
}
int main() {
cout << "顺序char int double " << endl;
print1();
cout << "顺序char double int " << endl;
print2(); // 1+(补7)+8+4 =20; 根据规则二不是 double(8) 的整数倍加4
return 0;
}
代码结果:
顺序char int double
16 1 4 8 # 规则一:1+(补3)+4+8=16
顺序char double int
24 1 8 4 #分析 1+(补7)+8+4 =20; 根据规则二不是 double(8) 的整数倍,需20+4=24
图例
3.结构体成员为结构体对象时
#include <iostream>
using namespace std;
struct X {
char a;
int b;
double c;
};
struct Y {
X b;
char a;
};
void print() {
cout << "X 的内存大小为:" << sizeof(X) << endl;
cout << "Y 的内存大小为:" << sizeof(Y) << endl;
}
int main () {
print();
return 0;
}
代码结果:
X 的内存大小为:16 # 1+(补3)+4+8=16
Y 的内存大小为:24 # b 前 a 后 :16+1+(补7,补至8的倍数) = 24
# 若 a 前 b 后:1+(补7,补至8的倍数)+16=24
分析:
计算Y的存储长度时,在存放第二个元素b时的初始位置是在double型的长度8的整数倍处,而非16的整数倍处
,即系统为b所分配的存储空间是第8~23个字节。
如果将Y的两个元素char型的a和X型的b调换定义顺序(b 前a 后),则系统为b分配的存储位置是第0~15个字节,为a分配的是第16个字节,加起来一共17个字节,不是最长基本类型double所占宽度8的整数倍,因此要补齐到8的整数倍,即24
。
修改默认对齐数
预处理指令: #pragma
#include <iostream>
using namespace std;
struct s1
{
double a;
char b;
int c;
};
#pragma pack(4) //数据成员对齐数 = min( 修改后的默认对齐数(4) ,该数据类型大小 )
struct s2
{
char a;
struct s1 s11;
double b;
};
#pragma pack()
int main()
{
printf("s1的大小:%d\n", sizeof(s1));
printf("s2的大小:%d\n", sizeof(s2));
return 0;
}
代码结果:
s1的大小:16
s2的大小:28 # 1+(补3,补至4的倍数)+16+8=28
分析:
- char a; // 地址从0开始,第一个字节存的是char
- struct s1 s11; // min( 4 ,max(double,char,int)) = 4 ,此时下标是1,1不是4的整数倍,补3个空字节,再存入s11(16个字节)
- double b; // min(4,8) = 4 ,此时下标是20,20是4的整数倍,再存入double
- 共存了1+3+16+8=28个字节,整体对齐数 = min(4,max( char, struct s1, double )) = 4,28是4的整数倍,不用补空字节。
int a[ ]中 a 与 &a区别
a 作为右值
时与&a[0] 意义相同
,代表数组首元素地址
;
&a
是取数组 a 的首地址
,&a+1是 &a + 5*sizeof(int);
a 是首元素首地址,即&a[0];&a 是数组首地址,虽然数值上相同,但是意义不相同。
#include <iostream>
using namespace std;
int main() {
int a[5] = {1,2,3,4,5};
int* ptr = (int* )(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1)); //2, 5
return 0;
}
捋清楚这个代码的结果
#include <iostream>
using namespace std;
struct Test {
int a;
char* b;
short c;
char d[2];
short e[4];
}*p;
int main() {
cout << "sizrof(short) = " << sizeof(short) << endl;
cout << "sizeof(struct Test) = " << sizeof(struct Test) << endl;
cout << "sizeof(*p) = " << sizeof(*p) << endl;
cout << "p = " << p << " p + 0x1 = " << p + 0x1 << endl;
cout << "(unsigned long)p = " << p << " unsigned long)p + 0x1 = " << p + 0x1;
return 0;
}
确定系统大小端
什么是大小端?
大端模式(Big.endian):
字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
小端模式(Little_endian):
字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。
图例
确定系统大小端代码
#include <iostream>
using namespace std;
int checkSystem() {
union check{
int i;
char ch;
}c;
c.i = 1;
return (c.ch == 1);
}
int main () {
int a = checkSystem();
cout << a; //输出为1 则为小端模式,0 则为大端模式
return 0;
}
一道考验题
在
大端模
式与小端模
式下的输出结果分别是什么?
#include <iostream>
using namespace std;
void print() {
union check{
int i;
char a[2];
}*p, u;
p = &u;
p->a[0] = 0x39;
p->a[1] = 0x38;
cout << "p->i = " << p->i;
}
int main() {
print();
return 0;
}
小端模式下代码运行结果:
p->i = 14393
结果分析
3889(h) = 14393(d)
再来一道网络难题
计算一下输出结果!
#include <iostream>
using namespace std;
int main() {
int a[4] = {1,2,3,4};
int* ptr1 = (int* )(&a + 1);
int* ptr2 = (int* )((long)a + 1);
printf("%x, %x", ptr1[-1], *ptr2);
return 0;
}
代码运行结果:
4, 2000000 # 在小端模式下
4, 100 # 在大端模式下
分析
ptr1:
将&a+1
的值强制转换成int*
类型,赋值给int*
类型的变量 ptr,ptrl 肯定指到数组a的下一个int类型数据了。ptr1[-1]被解析成* (ptr1-1)
,即ptrl往后退4字节,所以其值为0x4。
ptr2:
按照上面的讲解,(int)a+1的值是元素a[0]的第2个字节的地址
,然后把这个地址强制转换成int*
类型的值赋给ptr2,也就是说*ptr2
的值应该为元素a[0]的第2个字节开始的连续4字节的内容。
小端模式下
大端模式下
参考文章链接
二维数组
内存相当于尺子,最小单位为字节;二维数组也是存在尺子当中的。
看成这样很重要
来一道题
#include <iostream>
using namespace std;
int main() {
int a[3][2] = {(0,1), (2,3), (4,5)};
int* p = a[0];
/*for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j ++)
cout << a[i][j] << " ";
cout << endl;
}*/
printf("%d", p[0]); //1
return 0;
}
分析
花括号里面嵌套的是小括号,而不是花括号!这里是花括号里面嵌套了逗号表达式,
其实这个赋值就相当于int a[3][2]={1, 3, 5}。
再来一道号称很难做对的题 &p[4][2] - &a[4][2]
#include <stdio.h>
int main() {
int a[5][5];
int (*p)[4];
p = a;
printf("a_ptr = %#p, p_ptr = %#p\n", &a[4][2], &p[4][2]);
printf("%p, %d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
代码运行结果:
a_ptr = 0x7fff23bcfdc8, p_ptr = 0x7fff23bcfdb8
0xfffffffffffffffc, -4
分析理解
- 首先明确数组名 a 作为右值时,代表数组首元素地址,即 a 代表 a[0]地址。
&a[4][2]
表示的是&a[0][0]+4*5*sizeof(int)+ 2 * sizeof(int)
。- p 是指向四个元素的数组指针,也就是说 p+1 表示指针 p 向后移动“包含4个int类型元素的数组”。
- 由于p被初始化为
&a[0],所以&p[4][2]
表示的是&a[0][0]+4 *4sizeof(int)+2*sizeof(int)
二维数组与二维指针
二维数组参数与二维指针参数关系
函数指针
简单看下函数指针用法
#include <stdio.h>
#include <string.h>
char* fun(char* p1, char* p2) {
int i = 0;
i = strcmp(p1, p2);
if (i == 0) return p1;
else return p2;
}
int main() {
char * (*pf)(char* p1, char* p2);
pf = &fun;
(*pf)("aa", "bb");
return 0;
}
理解* (int* )&p = (int)Function
做了什么
#include <stdio.h>
void Function() {
printf("call Function");
}
int main() {
void (*p)();
* (int* )&p = (int)Function;
(*p)();
return 0;
}
分析
(int*)&p
表示将地址强制转换成指向int类型数据的指针。(int)Function
表示将函数的人口地址强制转换成int类型的数据。“* (int* ) &p= (int) Function;"
表示将函数的人口地址赋值给指针变量p。
再来一个(*(void(*)())0)()
的理解分析:
第1步:void( * ) (),
可以明白这是-一个函数指针类型。这个函数没有参数,没有返回值;第2步:(void( * ) ())0,
这是将0强制转换为函数指针类型,0是一个地址,也就是说一个函数保存在首地址为0的一段区域内
;第3步:(*(void(*)())0),
这是取0地址开始的一段内存里面的内容,其内容就是保存在首地址为0的一段区域内的函数;
-第4步:( * (void( * ) ())0)(),
这是函数调用。
函数指针数组与函数指针数组指针
函数指针数组
char* (*pf)(char* p)
不难理解!char* (*pf[3])(char* p)
就是函数指针数组。
pf并非指针,而是一个数组名,数组存储了3个指向函数的指针
函数指针数组指针
char* (*(*pf)[3])(char* p)
就是函数指针数组指针!!!
pf 是实实在在的指针,这个指针指向一个包含了3个元素的数组;这个数组里面存的是指向函数的指针
//指针函数数组指针的应用
#include <stdio.h>
#include <string.h>
char* func1(char* p) {
printf("%s\n", p);
return p;
}
char* func2(char* p) {
printf("%s\n", p);
return p;
}
char* func3(char* p) {
printf("%s\n", p);
return p;
}
int main() {
char* (*a[3])(char* p);
char* (*(*pf)[3])(char* p);
pf = &a;
a[0] = &func1;
a[1] = &func2;
a[2] = &func3;
pf[0][0]("func1"); //等价(*pf)[0]("func1")
pf[0][1]("func2");
pf[0][2]("func3");
return 0;
}
typedef
与回调函数相关的应用:C/C++ typedef 用法