0x01 结构声明
结构声明(structure declaration)描述了一个结构的组织布局。
struct book{char title[MAXTITL];char author[MAXAUTL];float value;
};
该声明描述了一个由两个字符数组和一个float类型变量组成的结构。该声明并未创建实际的数据对象,只描述了该对象由什么组成。
struct,它表明跟在其后的是一个结构,后面是一个可选的标记(该例中是 book),稍后程序中可以使用该标记引用该结构。
我们在后面的程序中可以这样声明:
struct book library;
这把library声明为一个使用book结构布局的结构变量。
#include "stdio.h"#define MAXTITL 30
#define MAXAUTL 30struct book{char title[MAXTITL];char author[MAXAUTL];float value;
};int a;
struct book book2;
int main()
{struct book book1={"Hello","d1l1endh",7.6};printf("title=%s\n",book1.title);printf("author=%s\n",book1.author);printf("value=%f\n",book1.value);return 0;
}
反汇编我们看看结构声明会产生什么:
struct book相当于一个类型,和int 相似,book2相当于a,结构布局告诉编译器如何表示数据,但是它并未让编译器为数据分配空间。
0x02 定义结构变量
struct book book2;
编译器执行这行代码便创建了一个结构变量book2。编译器使用book模
板为该变量分配空间:一个内含MAXTITL个元素的char数组、一个内含
MAXAUTL个元素的char数组和一个float类型的变量。这些存储空间都与一
个名称book2结合在一起
struct book book2;
是以下声明的简化:
struct book {
char title[MAXTITL];
char author[AXAUTL];
float value;
} book2;
换言之,声明结构的过程和定义结构变量的过程可以组合成一个步骤。如果打算多次使用结构模板,就要使用带标记的形式;或者,使用typedef。
2.1 初始化结构
初始化一个结构变量(ANSI之前,不能用自动变量初始化结构;ANSI之后可以用任意存储类别)与初始化数组的语法类似:
struct book book1={"Hello","d1l1endh",7.6};
2.2 访问结构成员
使用结构成员运算符——点(.)访问结构中的成员。
0x03 嵌套结构
在一个结构中包含另一个结构
#include "stdio.h"#define LEN 30struct names{char first[LEN];char last[LEN];
};struct guy{struct names handle;char favfood[LEN];char job[LEN];float income;};int main()
{struct guy fellow={{"Ell","end"},"salmon","programmer",100000};printf("firset=%s\n",fellow.handle.first);printf("job=%s\n",fellow.job);printf("income=%f\n",fellow.income);return 0;
}
访问嵌套结构的成员,这需要使用两次点运算符。handle是fellow的成员,first是handle的成员
0x04 指向结构的指针
4.1 声明和初始化指针
我们定义一个int类型指针是:
int *p;
结构也是一种类型,所以声明结构体指针很简单
struct guy *him;
struct guy 是一种类型,相当于int,这个语法和其他指针声明一样。该声明并未创建一个新的结构,但是指针him现在可以指向任意现有的guy类型的结构。如果barney是一个guy类型的结构,可以这样写:
him = &barney;
和数组不同的是,结构名并不是结构的地址,因此要在结构名前面加上&运算符。
4.2 用指针访问成员
使用->运算符。
->运算符后面的结构指针和.运算符后面的结构名工作方式相同
如果him == &barney,那么him->income 即是 barney.income
4.3 向函数传递结构的信息
这里我们重点说传递结构的地址和传递结构
传递结构的地址
#include "stdio.h"#define LEN 30struct names{char first[LEN];char last[LEN];
};struct guy{struct names handle;char favfood[LEN];char job[LEN];float income;};double sum(struct guy *);int main()
{struct guy fellow={{"Ell","end"},"salmon","programmer",100000};double sum_income=sum(&fellow);//printf("firset=%s\n",fellow.handle.first);//printf("job=%s\n",fellow.job);printf("sum_income=%f\n",sum_income);return 0;
}double sum(struct guy *people)
{return (people->income+people->income);
}
因为结构名不是其地址别名,所以我们必须用&运算符,然后通过->运算符获取值
传递结构
#include "stdio.h"#define LEN 30struct names{char first[LEN];char last[LEN];
};struct guy{struct names handle;char favfood[LEN];char job[LEN];float income;};double sum(struct guy );int main()
{struct guy fellow={{"Ell","end"},"salmon","programmer",100000};double sum_income=sum(fellow);//printf("firset=%s\n",fellow.handle.first);//printf("job=%s\n",fellow.job);printf("sum_income=%f\n",sum_income);return 0;
}double sum(struct guy people)
{return (people.income+people.income);
}
传递结构直接把结构名当参数就行
0x05 typedef
typedef工具是一个高级数据特性,利用typedef可以为某一类型自定义名称。
typedef unsigned char BYTE;
随后,便可使用BYTE来定义变量:
BYTE x, y[10], * z;
该定义的作用域取决于typedef定义所在的位置。
0x06 函数与指针
声明一个函数指针时,必须声明指针指向的函数类型。为了指明函数类型,要指明函数签名,即函数的返回类型和形参类型。
void ToUpper(char *); // 把字符串中的字符转换成大写字符
ToUpper()函数的类型是“带char * 类型参数、返回类型是void的数”。
void (*pf)(char *); // pf 是一个指向函数的指针
第1对圆括号把*和pf括起来,表明pf是一个指向函数的指针
把**函数名ToUpper替换为表达式(*pf)**是创建指向函数指针最简单的方式。
所以,如果想声明一个指向某类型函数的指针,可以写出该函数的原型后把函数名替换成(*pf)形式的表达式,创建函数指针声明。
#include "stdio.h"void add(int a,int b);void main(void)
{int a=4,b=3,s=0;void (*funp)(int a,int b);funp=add;(*funp)(a,b);funp(a,b);}void add(int a,int b)
{printf("a+b=%d\n",a+b);}
声明一个函数指针时,必须声明指针指向的函数类型。包括返回值、参数,如果想声明一个指向某类型函数的指针,可以写出该函数的原型后把函数名替换成(*pf)形式的表达式,这样就声明好一个函数指针了。用函数指针调用函数是有两种方式:
- 指针名(参数)
- (* 指针名)(参数)
我们反汇编看一下
9: void (*funp)(int a,int b);
10: funp=add;
00410B2D C7 45 F0 0F 10 40 00 mov dword ptr [ebp-10h],offset @ILT+10(_add) (0040100f)
11: (*funp)(a,b);
00410B34 8B F4 mov esi,esp
00410B36 8B 45 F8 mov eax,dword ptr [ebp-8]
00410B39 50 push eax
00410B3A 8B 4D FC mov ecx,dword ptr [ebp-4]
00410B3D 51 push ecx
00410B3E FF 55 F0 call dword ptr [ebp-10h]
00410B41 83 C4 08 add esp,8
00410B44 3B F4 cmp esi,esp
00410B46 E8 E5 06 FF FF call __chkesp (00401230)
可以看出,把首地址赋值给我们的函数指针,也就是把函数首地址放到dword ptr [ebp-10h]
里,调用的时候直接call dword ptr [ebp-10h]
,和平常调用函数一样