结构体、共用体、Makefile

一、结构体

1.1 结构体变量的初始化和赋值的方式

struct Student{
    int id;
    char name[32];
    int score;
}
struct Student s;
s.id = 1001;
strcpy(s.name,"zhangsan");
s.score = 98;
struct Student{
    int id;
    char name[32];
    int score;
}s;
s.id = 1001;
strcpy(s.name,"zhangsan");
s.score = 98;
struct Student{
    int id;
    char name[32];
    int score;
};
struct Struct s = {1001,"zhangsan",98};
struct Student{
    int id;
    char name[32];
    int score;
}s = {1001,"zhangsan",98};
struct Student{
    int id;
    char name[32];
    int score;
};
struct Student s={
    .id = 1001,
    .score = 98;
};
struct Student{
    int id;
    char name[32];
    int score;
};
struct Student s;
s = (struct Student){1001,"zhangsan",98};

1.2 结构体数组的初始化和赋值的方式

struct Student{
    int id;
    char name[32];
    int score;
};

struct Student s[2];
s[1].id = 1001;
strcpy(s[1].name,"zhangsan");
s[1].score = 100;

s[2].id = 1002;
strcpy(s[2].name,"lisi");
s[1].score = 101;
struct Student{
    int id;
    char name[32];
    int score;
};
struct Student s[2]={
    {1001,"zhangsan",98},
    {1002,"lisi",99}
};
struct Student{
    int id;
    char name[32];
    int score;
};
struct Student s[5] = {
    [0] = {1001,"zhangsan",98},
    [3] = {1002,"lisi",100}
};
struct Student{
    int id;
    char name[32];
    int score;
};
struct Struct s[5] = {
    [0] = {
        .id = 1001;
        .score = 99
    },
    [3] = {
        .name = "wangwu"
    }
};

1.3 C语言的结构体中不允许定义函数

C++的结构体中允许定义函数,但是C语言中不允许,C语言的结构体中可以有函数指针

#include <stdio.h>

int my_add(int x,int y){
	return x+y;
}

typedef struct Test{
	int id;
	char name[32];
	int score;
	int (*p)(int ,int );//定义函数指针
}Test_t;

int main(int argc, const char *argv[])
{
	Test_t t;
	t.p = my_add;//让函数指针指向函数
	printf("%d\n",t.p(20,30));//通过函数指针调用函数

	return 0;
}
结果:
50

1.4 结构体对齐

----------------------------------32位系统------------------------------------------------

64位系统想将程序按32位编译,需要加编译选项 -m32

对齐规则:

1.如果结构体成员都是小于4字节,则按最大成员对齐

2.如果结构体有成员大于等于4字节,都按4字节对齐

3.要特别注意 char 和 short 连续存储问题:

每个成员距离首地址的偏移量必须是成员类型大小的整数倍!!

struct Test{
    char a;
    char b;
    char c;
}; 
共占3字节
struct Test{
    short b;
    char a;
};
共占4字节
struct Test{
    short b;
    char a;
    char c;
};
共占4字节
struct Test{
    char c;
    short b;
    char a;
};
共占6字节
struct Test{
    char c;
    int a;
    char b;
};
共占12字节
struct Test{
    char a;
    int c;
};
共占8字节
struct Test{
    char c;
    long long a;
};
共占12位

struct Test{
    char a;
    char b;
    short c;
    int d;
};
共占8字节
struct Test{
    char a;
    short c;
    char b;
    int d;
};
共占12字节

---------------------------------------64位系统--------------------------------------------

64位系统的对齐规则基本和32位系统一样,

只不过不是4位对齐,而是按照最大成员进行对齐

且每个成员距离首地址的偏移量也必须是成员类型大小的整数倍!!

struct Test{
    char a;
    long double b;
};
共占32字节
struct Test{
    char a;
    int b;
    char c;
    short d;
    long double e;
};
共占32字节

结构体中嵌套结构体时的对齐规则:

struct A{
    short x;
    short y;
    short z;
};
struct B{
    struct A m;
    char n;
    int k;
};

结构体A自身是2字节对齐,当把A放在B中的时候,由于B是4字节对齐的

相当于B为了满足自身的对齐,需要额外分给A两个字节(下图左紫色部分)

这两个字节是属于B的,所以B的成员可以占用

struct C{
    int x;
    short y;
};
struct D{
    struct C m;
    char n;
    int k;
};

结构体C自身就是4字节对齐,当把C放在D中的时候,D并没有为了满足自身的对齐,

而额外给C分配空间,因为C满足自身对齐时本来就浪费了两个字节(上图右紫色部分)

这两个字节时属于C的,所以D成员不可以占用

1.5 结构体位域

压缩结构体的一种手段,嵌入式底层开发时经常用到

#include <stdio.h>

struct LED{
	unsigned char led0:1;
	unsigned char led1:1;
	unsigned char led2:1;
	unsigned char led3:1;
	unsigned char led4:1;
	unsigned char led5:1;
	unsigned char led6:1;
	unsigned char led7:1;
};

int main(int argc, const char *argv[])
{
	printf("%ld\n",sizeof(struct LED));
	//8字节
	struct LED my_led;

	//点亮3号灯
	my_led.led3 = 1;

	//熄灭4号灯
	my_led.led4 = 0;

	//压缩之后成员的存储范围也随之变小了
	my_led.led5 = 2;//超范围了
	printf("%d\n",my_led.led5);//0

	return 0;
}
结果:
1
0

二、共用体(联合体)--union

1.定义共用体类型的格式以及使用共用体定义变量的格式,都和结构体一模一样,只不过把关键字struct 换成了union

2.共用体中的所有成员时共用同一块内存空间

3.共用体的所有成员首地址相同

4.共用体的大小取决于共用体中成员的最大类型

union 共用体类型名{
      数据类型1 成员1;
      数据类型2 成员2;
      ...
      数据类型n 成员n
};

因为共用体中所有成员都是用同一块内存空间

修改一个成员 其他成员的值也会受影响

所以,使用共用体时,我们不会同时使用多个成员

#include <stdio.h>

union Test{
	char a;
	short b;
	int c;
	long long d;
};

int main(int argc, const char *argv[])
{
	printf("%ld\n",sizeof(union Test));//8
	union Test t;
	t.a = 100;
	printf("t.a = %d\n",t.a);

	t.b = 300;
	printf("t.b = %d\n",t.b);

	printf("t.a = %d\n",t.a);


	//成员首地址都是一样的
	printf("%p\n",&t.a);
	printf("%p\n",&t.b);
	printf("%p\n",&t.c);
	printf("%p\n",&t.d);

	return 0;
}
结果:
8
t.a = 100
t.b = 300
t.a = 44
0x7ffcdcbc8800
0x7ffcdcbc8800
0x7ffcdcbc8800
0x7ffcdcbc8800

#include <stdio.h>

union Test{
	char a;
	int  d;
};

int main(int argc, const char *argv[])
{
	union Test t;
	t.d = 0x12345678;
	if(t.a == 0x78)
	{
		printf("小端\n");
	}
	else if(t.a == 0x12)
	{
		printf("大端\n");
	}
	
	return 0;
}
结果:
小端

三、Makefile

3.1 什么是Makefile?

Makefile就是一个文件,他能体现出我们是否具有大型项目开发的能力

Makefile文件中存放的是整个项目编译的规则

它可以根据文件的时间戳来决定哪些文件需要重新编译(更新时间戳)

3.2 什么时Make?

make是一个可执行文件 是/usr/bin/make,是用来解析Makefile文件的,如果系统没有make,可以自己安装 sudo apt-get install make

3.3 Makefile 的语法

3.3.1 标签式语法

标签:		#标签必须顶满格写
    指令	#指令必须以 tab 键开头
标签1:
    指令1
标签2:
    指令2
...
标签n:
    指令n

执行时,直接使用make 命令即可

make 会在当前路径下,去自动寻找。名为Makefile 的文件来解析和执行

make 默认执行时 执行第一个标签下的内容

也可以使用 make 标签名 方式来指定执行哪个标签下的内容

如果文件名不叫Makefile或者makefile ,也可以使用 make -f 文件名 方式来指定解析哪些文件

3.3.2 目标:依赖 式语法

目标:依赖	#目标必须顶满格写
    指令	#指令必须以tab键开头
app:02test.c
    gcc 02test.c -o app

例如:

test.c

编译步骤:预处理 编译 汇编 链接

预处理: 展开头文件 删除注释 替换宏定义

gcc -E test.c -o test.i

编译:词法分析 语法分析 查错

gcc -S test.i -o test.s

汇编:将汇编文件生成二进制的文件

gcc -c test.s -o test.o

链接:链接多个目标文件和库文件 生成最终的可执行文件

gcc test.o -o app

以上流程为例,写Makefile来看Makefile的执行方式

app:test.o
    gcc test.o -o app
test.o:test.s
    gcc test.s -c -o test.o
test.s:test.i
    gcc test.i -S -o test.s
test.i:test.c
    gcc test.c -E -o test.i

make在解析Makefile时,会进行自动推导

app-->依赖-->test.o-->依赖-->test.s-->依赖-->test.i-->依赖-->test.c

c i s o

所以最终执行的顺序:

gcc -E test.c -o test.i

gcc -S test.i -o test.s

gcc -c test.s -o test.o

gcc test.o -o app

Makefile的目标又分为 目标和伪目标

目标:最终执行完后会生成目标文件的 如app

伪目标:最终执行完后不会生成目标文件的 如clean

------------------------------------------------------------------------------------

模拟大型项目,使用Makefile编写规则来编译项目

---------------(例子在最后)--------------------

Makefile中使用变量的方式和shell类似

VALUE = xxx

引用变量的值

$(VALUE) 或者 ${变量名}

变量的赋值方式:

这种赋值方式会将变量在整个文件中所有的赋值位置都找到
最后一次赋值的结果取出 赋给其他变量
V1=hello
V2=$(V1)
V1=world
all:
    @echo $(V2) //world
//@表示取消命令的回显
V1=hello
V2:=$(V1)
V1=world
all:
    @echo $(V2) //hello
V1=hello
V2=world
V1+=$(V2)
all:
    @echo $(V1) //hello world
如果前面已经给变量赋值过了,本次赋值不再生效
V1?=hello
V1?=world //赋值不生效
all:
    @echo $(V1) //hello

3.5 Makefile中的特殊变量

$@ 目标文件

$^ 所有依赖文件

$< 第一个依赖文件

CC=gcc
OBJ=main.o hqyj1.o hqyj2.o hqyj3.o
TARGET=app
INCLUDE=-I ../include
FLAG=-c -o

$(TARGET):$(OBJ)
	$(CC) $^ -o $@
main.o:main.c
	$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj1.o:hqyj1.c
	$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj2.o:hqyj2.c
	$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj3.o:hqyj3.c
	$(CC) $(INCLUDE) $< $(FLAG) $@

clean:
	rm $(OBJ) $(TARGET)

3.6 Makefile 中的通配符

* Makefile中执行shell命令时使用的通配符 ( 通配任意长度的任意字符 )

LIST=$(shell ls *.c)
all:
    @echo $(LIST)

% Makefile中的通配符 ( 通配任意长度的任意字符 )

3.7 可以在Makefile包含其他的.mk文件

-include 要包含的文件名

-include ../config.mk

表示将上一级路径下的config.mk文件中的内容包含到当前Makefile文件中

---------------------------Makefile例子-------------------------------------

app:main.o hqyj1.o hqyj2.o hqyj3.o
	gcc main.o hqyj1.o hqyj2.o hqyj3.o -o app
main.o:main.c
	gcc -I ../include main.c -c -o main.o
hqyj1.o:hqyj1.c
	gcc -I ../include hqyj1.c -c -o hqyj1.o
hqyj2.o:hqyj2.c
	gcc -I ../include hqyj2.c -c -o hqyj2.o
hqyj3.o:hqyj3.c
	gcc -I ../include hqyj3.c -c -o hqyj3.o

clean:
	rm main.o hqyj1.o hqyj2.o hqyj3.o app
CC=gcc
OBJ=main.o hqyj1.o hqyj2.o hqyj3.o
TARGET=app
INCLUDE=-I ../include
FLAG=-c -o

$(TARGET):$(OBJ)
	$(CC) $(OBJ) -o $(TARGET)
main.o:main.c
	$(CC) $(INCLUDE) main.c $(FLAG) main.o
hqyj1.o:hqyj1.c
	$(CC) $(INCLUDE) hqyj1.c $(FLAG) hqyj1.o
hqyj2.o:hqyj2.c
	$(CC) $(INCLUDE) hqyj2.c $(FLAG) hqyj2.o
hqyj3.o:hqyj3.c
	$(CC) $(INCLUDE) hqyj3.c $(FLAG) hqyj3.o

clean:
	rm $(OBJ) $(TARGET)
CC=gcc
OBJ=main.o hqyj1.o hqyj2.o hqyj3.o
TARGET=app
INCLUDE=-I ../include
FLAG=-c -o

$(TARGET):$(OBJ)
	$(CC) $^ -o $@
main.o:main.c
	$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj1.o:hqyj1.c
	$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj2.o:hqyj2.c
	$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj3.o:hqyj3.c
	$(CC) $(INCLUDE) $< $(FLAG) $@

clean:
	rm $(OBJ) $(TARGET)
CC=gcc
OBJ=main.o hqyj1.o hqyj2.o hqyj3.o
TARGET=app
INCLUDE=-I ../include
FLAG=-c -o

$(TARGET):$(OBJ)
	$(CC) $^ -o $@
%.o:%.c
	$(CC) $(INCLUDE) $< $(FLAG) $@

clean:
	rm $(OBJ) $(TARGET)
-include ../config.mk 
OBJ=main.o hqyj1.o hqyj2.o hqyj3.o
TARGET=app

$(TARGET):$(OBJ)
	$(CC) $^ -o $@
%.o:%.c
	$(CC) $(INCLUDE) $< $(FLAG) $@

clean:
	rm $(OBJ) $(TARGET)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值