GCC编译器
gcc编译c文件的流程:
1.预处理。处理头文件展开,宏替换等动作,不会进行任何语法检查。
gcc hello.c -E -o hello.i
-E 只进行预处理操作
-o 重命名输出文件名字
hello.i .i后缀表示只进行预处理后的文件
2.编译。使用c编译工具,将预处理后的c代码编译为汇编代码。
gg hello.i -S -o hello.s
-S 只进行c编译为汇编
.s 后缀用于表示汇编文件(该动作需要进行语法检查)
3.汇编。使用汇编器将汇编代码编译为二进制目标文件,不能单独运行。
gcc hello.s -c -o hello.o
-c 仅进行汇编,生成二进制目标文件
.o 表示二进制目标文件
4.链接。将多个二进制目标文件与库链接,生成可以执行的二进制文件。
gcc hello.o -o hello.out
gcc中的其他选项:
-I 用于指定头文件的搜索路径
gcc hello.c -c -o hello.o -I.linc
-l 用于指定链接第三方库的名字
-L 用于指定链接第三方库的路径
-O 用于指定编译优化等级 -O0不优化 -O1 -O2 -O3 valatile防止编译器优化
-g 用于编译时附加调试数据,主要用于gdb调试
GCC组件: 有4个组成部件
编译器 : C ---> 汇编
汇编器 : 汇编---> 二进制机器码
链接器 : 链接多个二进制机器码 和 库 构成 可以运行的二进制程序
C库 : 一些有gcc提供的 常用的一些函数
编译报错:
1. 头文件错误 预处理阶段出现 通常为头文件路径错误或文件名写错了
hello.c:5:19: fatal error: inc/a.h: 没有那个文件或目录
compilation terminated.
2. 语法错误 在 编译阶段出现 通常为 标点符号或中文问题
hello.c: In function ‘main’:
hello.c:10:2: error: expected ‘;’ before ‘func’
func();
^
中文符号问题
hello.c: In function ‘main’:
hello.c:9:2: error: stray ‘\357’ in program
printf("PI=%.2f\n", PI);
^
hello.c:9:2: error: stray ‘\274’ in program
hello.c:9:2: error: stray ‘\233’ in program
hello.c:10:2: error: expected ‘;’ before ‘func’
func();
^
3. 未定义标识符 变量/函数没有定义或声明就使用
hello.c: In function ‘main’:
hello.c:12:19: error: ‘pelpe’ undeclared (first use in this function)
printf("%.2d\n", pelpe);
^
hello.c:12:19: note: each undeclared identifier is reported only once for each function it appears in
4. 归档错误/ 链接错误 引用了其他文件或库中的函数 而没有声明 或 函数名字错误
/tmp/cclYp5VM.o:在函数‘main’中:
hello.c:(.text+0x42):对‘func’未定义的引用
collect2: error: ld returned 1 exit status
逻辑错误: bug 程序可以运行 但 没有达到预期效果甚至出现错误
调试工具: 软件工具 与 硬件工具
printf打印法调试
GDB 调试器: 软件调试工具
字符界面调试功能
1. 编译时添加-g 选择 附加调试信息
gcc zhishufenjie.c -g -o gdb.out
2. 使用gdb运行调试程序
gdb gdb.out
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from gdb.out...done.
(gdb)
gdb 命令
l 查看程序源码
设置断点 断点: 程序 运行到这里就会暂停
b 行号
info b 查看所有的断点
运行代码
r
查看程序中变量的值
单步运行: 一次执行一行C代码
(gdb) n 运行一行
(gdb) s 若这一行是函数调用,s则进入函数
恢复程序运行
(gdb) c
删除断点
del 1 删除断点1
quit 退出gdb
指针: C语言的灵魂 手术刀
本质就是 一个直接访问内存 的工具 只有C语言有
程序的本质:就是操作内存
指针是啥?
1. 指针变量 : 一个变量 存放一个内存的地址
2. 一个内存地址 指针常量
指针变量的定义:
存储类型 指针指向的空间的数据类型 * 变量名;
示例:
int *p = &a;
指针变量的 赋值: 类型匹配才可以赋值
物理意义: 与指定的内存 建立联系
&地址运算: 变量可取地址 有内存的量
得到变量的内存地址
野指针: 一个指针 指向的内存不可知或不确定或非法(不可访问)
空指针: 一个指针 指向内存 0地址(不允许访问) 即这个指针中的值是0 int *p = NULL;
宏 NULL 空指针 (void *)0 通常在程序中 表示一个指针 没有建立任何指向关系
void * 类型 : 表示 万能指针, 一个可以指向 任意类型的指针
指针的访问: * 指针取值运算符
*指针变量 即等同于 指针指向的目标(对象)
1 * 只能对指针操作 只能对有具体类型的指针
2 指针必须有建立指向关系 不能对空指针 野指针进行*操作
*空指针 段错误/空指针异常
*野指针 结果未知 大概率段错误
void * 类型的指针 可以赋值 void *p = &a; *p 不行 *(int*)p 可以的
不能*操作 通常是进行强制类型转换 为指定类型的 *
int b;
int *p = &b;
*&b; 等同于 b
* 是 & 的逆运算
&*p; 等同于 &b
& 是 * 的逆运算
指针的算数运算:
指针 +或- 一个整数 其结果仍然是一个指针
其运算本质就是 地址的移动 + 向大地址方向移动 - 小地址方向移动
其移动的字节大小 由其指向的类型长度决定 +1 / -1 移动一个指向的类型
指针的 ++ -- 运算 p++; *p++; (*p)++;
指针 与 指针的 运算
1. 大地址 - 小地址 结果是一个整数 其大小为 这两个地址之间相隔的 指向的类型个数
(1) 这两个地址 必须在同一段连续的内存空间上
(2) 这两个地址 类型要一致
2. 关系运算 本质是 判断 这个两个地址 在内存中的位置
p > q p地址 在 q地址的 大方向
p < q p地址 在 q地址的 小方向
p == q p和q指针指向同一个内存
p != q p和q指针指向不同的内存
p >= q p地址 在 q地址的 大方向 或 相同
p <= q p地址 在 q地址的 小方向 或 相同
一级指针和一维数组:
数组名可以当指针用,指针变量可以做数组用,区别在于指针变量可以重新赋值,而数组名不行。
总结指针的 5个运算:
= 赋值运算 建立指向关系
* 取指向对象 *p
+/- n 地址移动
p-q 指针相减
p>q 关系运算
指针占用的内存大小:
指针变量指向的类型 不影响其 占用的内存大小
若 是32位机器 地址大小为 4字节
若 是64位机器 地址大小为 8字节
指针数组与二级指针
指针数组:本质是一个数组,其元素是指针
定义:存储类型 元素类型 数组名[元素个数]={};
auto cosnt char *ps[6]={"Beijing","Moscow","New York","London",""};
遍历指针数组:
for(int i= 0; i<5; i++)
printf("%s ",ps[i]);
指针方式遍历:
//二级指针
const char ** pps = ps;
//遍历指针数组:
for( int i = 0; i<5; i++)
printf("%s ",*pps++);
printf("\n");
数组指针与二维数组:
二维数组:int arr[2][3]={1,2,3,4,5,6};
数组指针:一个指针指向一个一维数组
指针与函数:
指针函数:本质是一个函数,返回值是一个指针
代码段:存储代码
函数名:1.代指这个函数本身
2.代指这个函数的首地址
函数类型:除去函数名以及形参名等名字剩下的就是函数的类型。
函数指针定义:指向的函数返回值类型(*函数指针名)(函数形参类型列表)=对应类型的函数;
函数指针应用:回调函数多用于写接口
接口:API,写接口时,该接口的有些内容还没有确定,因此通过函数指针的方式来传递参数
回调函数:一个函数其参数有函数指针类型
函数指针数组:本质是一个数组,数组中元素是函数指针
递归函数:
函数中存在自己调用自己(递归关系),每次自己调用自己,问题规模在逐渐减小,向临界条件趋近。
临界条件:退出自己调用自己的条件
递归的特性:本质就是对栈区空间的利用
递归过于深入可能导致栈区溢出段错误,效率不高
在一些特定情况下,使用递归可以简化代码量。
结构体:是一个构造数据类型
引入:描述一个人
姓名name 性别sex 年龄age 身高high
char[20] char[10] int float
由编程者先定义一个结构体类型然后利用定义的类型定义变量。
成员:结构体中的数据对象称成员
1.定义一个人的结构体类型
struct 类型名
{//成员表
成员类型成员名;
char sex[10];
int age;
float high;
};
2.使用类型定义变量
//用结构体类型定义变量才会分配内存空间
struct people zhang3;
//定义变量并初始化
struct people li4={"李四","男",18,1.80};
3.结构体变量成员引用
.成员引用运算符
结构体变量名.成员名;
结构体变量类型相同可以直接赋值;
4.结构体指针
一个指针指向一个结构体,通过结构体指针访问结构体
-> 结构体指针成员引用运算符
5.类型别名 给类型取别名
typedef 原类型名 别名;
//定义类型别名
typedef struct people ren_t;
typedef struct people *ren_p
定义类型同时就取别名
typedef struct people
{//成员表
char name[20];
char sex[10];
int age;
float high;
}ren_t;
定义类型同时就定义变量
struct people
{//成员表
char name[20];
char sex[10];
int age;
float high;
}zhang3;
定义类型同时就定义变量同时初始化
struct people
{//成员表
char name[20];
char sex[10];
int age;
float high;
}zhang3={"张三","男",18,1.80};
关于结构体的类型:
结构体的类型由定义结构体类型时给定的名字决定,名字不同则类型不同
无名结构体:
struct
{//成员表
char name[20];
char sex[10];
int age;
float high;
}zhang3;
结构体存储类型:
1.结构体类型成员顺序在内存中按同样的顺序排列
2.遵循紧凑排列
3.默认遵循自动字节对齐
字节对齐:计算机为了方便快速找到一个变量的位置因此而引入字节对齐。
多字节类型(int,short)要求其首地址应该能够被其长度整除。
#pragma pack (2) /*指定按2字节对齐,后面定义的结构体均遵循这个规则*/
#pragma pack () /*取消指定对齐,恢复缺省对齐 自动字节对齐*/
结构体数组:本质是一个数组,其元素是结构体
struct people arr[5];
结构体指针数组
本质一个数组 其元素 结构体指针
struct people *arr[5];
0号元素的name arr[0]->name
结构体数组指针
本质一个指针 指向 一个数组 数组元素是结构体
struct people (*p)[5];
访问p指向的数组中的 0号 结构体中的 name
(*p)[0].name ;
结构体中的成员是一个结构体:
struct stu
{
struct people ren;
int id;
};
struct stu zhang3;
zhang3.ren.name;
struct people
{//成员表
char name[20];
char sex[10];
int age;
float high;
struct people *next;
};
共用体:跟结构体类似,区别在于共用体成员公用同一块内存空间,其占用内存大小由最大空间的那个成员决定。
定义公用体类型:
union 共用体类型名
{
成员类型 成员名;
......
}
定义公用体变量:union公用体类型名 变量名;
枚举类型:
对常量进行去别名
enum 类型名
{
常量表;
};
动态内存分配:就是向操作系统申请堆区内存,可以在程序运行中动态的申请你想要的大小。
内存的申请与释放:
#include <stdlib.h>
void *malloc(size_t size); //申请内存
void free(void *ptr); //释放内存
size:要申请的内存大小,单位字节
void *:返回值是一个无类型指针
申请成功时,该指针指向你申请的堆区内存
申请失败,返回NULL指针
使用堆区内存时,必须先将堆区内存地址强制转换为你要的内存然后才能访问
ptr:你申请时系统给的堆区地址,释放是需要作为free的参数
关于申请与释放注意事项:
1.申请时给的堆区地址,在没有释放前,不能丢失,丢失堆区指针,将导致内存泄漏,生命周期从申请开始到释放结束
2.已经释放的堆区内存不能再访问了,继续访问则段错误
3.不能重复释放
4.不能部分释放
通常 malloc 申请的空间
struct people* p = (struct people*)malloc(sizeof(struct people));
p->name
makefile工程管理器:用于辅助编译的工具
对多文件编译时,可以自动对改变的.c文件进行重新编译,通过对文件的时间戳来判断.c文件与.o文件的新旧来确定是否需要重新编译。
使用:make
make 目标
编写makefile,来配置工程管理器的编译过程
编写规则:
目标:依赖
<table>shell命令 用于将依赖编译为目标文件的shell文件
变量定义:shell定义变量类似
变量名=值