既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
编译和链接详解
1. 预处理 选项gcc -E test.c -o test.i
预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。
2. 编译 选项 gcc -S test.c
编译完成之后就停下来,结果保存在test.s中。
词法分析
词法分析阶段是编译过程的第一个阶段。这个阶段的任务是从左到右一个字符一个字符地读入源程序,即对构成源程序的字符流进行扫描然后根据构词规则识别单词(也称单词符号或符号)。
语法分析
语法分析是编译过程的一个逻辑阶段。语法分析的任务是在词法分析的基础上将单词序列组合成各类语法短语,如“程序”,“语句”,“表达式”等等.
语义分析
语义分析是编译过程的一个逻辑阶段.
举例:
3. 汇编 gcc -c test.c
汇编完成之后就停下来,结果保存在test.o中。
形成符号表
链接
合并段表
运行环境
程序执行的过程:
程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
程序的执行便开始。接着便调用main函数。
开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
终止程序。正常终止main函数;也有可能是意外终止。
预处理
预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
举例使用
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i = 0;
FILE* pf = fopen("log.txt", "w");
if (pf == NULL)
{
perror("fopen");
return EXIT_FAILURE;
//EXIT_SUCCESS;
}
for (i = 0; i < 10; i++)
{
fprintf(pf, "file:%s line=%d date:%s time:%s i=%d\n", __FILE__, __LINE__, __DATE__, __TIME__, i);
}
fclose(pf);
pf = NULL;
return 0;
}
#define
语法:
#define name stuff
提问:
在define定义标识符的时候,要不要在最后加上;?
比如:
#define MAX 1000;
语法错误:
if(condition)
max = MAX;
else
max = 0;
所以建议不要加上;,这样容易导致问题。
#define 定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
下面是宏的申明方式:
#define name( parament-list ) stuff
其中的parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分.
例题演示
例题1
#include <stdio.h>
//#define SQUARE(X) X*X
#define SQUARE(X) ((X)*(X))
int main()
{
int r = SQUARE(5+1);
//int r = ((5 + 1) * (5 + 1));
//int r = 5 + 1 * 5 + 1;//11
printf("%d\n", r);
return 0;
}
例题2
//#define DOUBLE(X) (X)+(X)
#define DOUBLE(X) ((X)+(X))
int main()
{
int r = 10*DOUBLE(3);
//int r = 10 * (3) + (3);//33
//int r = (3 * 2) + (3 * 2);
printf("%d\n", r);//60
return 0;
}
例题3
#define M 100
#define DOUBLE(X) ((X)+(X))
int main()
{
//"M";
//"DOUBLE(3)";
int r=DOUBLE(M+2);
//((100 + 2)+(100 + 2));
printf("%d", r);
return 0;
}
总结:
所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
#define 替换规则
在程序中扩展 #define 定义符号和宏时,需要涉及几个步骤:
1.在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,它们首先被替换。
2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3.最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就重复上述处理过程。
注意:
宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是对于宏,不能出现递归。
当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索。
#和##
#的作用 : 把宏参数变为一个字符串
#define PRINT(N) printf("the value of "#N" is %d\n", N)
#define PRINT(N, FORMAT) printf("the value of "#N" is "FORMAT"\n", N)
int main()
{
/*printf("hello world\n");
printf("hello ""world\n");*/
int a = 10;
PRINT(a, "%d");
//PRINT(a);
//printf("the value of ""a"" is %d\n", a);
//print(a);
//printf("the value of a is %d\n", a);
float f = 3.14f;
PRINT(f, "%lf");
//int b = 20;
//PRINT(b);
//printf("the value of ""b"" is %d\n", b);
//print(b);
//printf("the value of b is %d\n", b);
return 0;
}
#的作用 :
##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。
#define CAT(Class, Num) Class##Num
int main()
{
int Class106 = 100;
printf("%d\n", CAT(Class, 106));
//printf("%d\n", Class106);
return 0;
}
带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
x+1;//不带副作用
x++;//带有副作用
例如:
#include <stdio.h>
int main()
{
int a = 2;
int b = a + 1;//1
int b = ++a;//2
return 0;
}
MAX 宏可以证明具有副作用的参数所引起的问题。
- #define MAX(a, b) ( (a) > (b) ? (a) : (b) )
- …
- x = 5; y = 8; z = MAX(x++, y++);
- printf(“x=%d y=%d z=%d\n”, x, y, z);//输出的结果是什么
//z = ( (x++) > (y++) ? (x++) : (y++));
结果:x=6 y=10 z=9
#include <stdio.h>
int main()
{
int a = 5;
int b = 8;
int c = MAX(a++, b++);
//int c = ((a++) > (b++) ? (a++) : (b++));
// 9 5 8 × 9
printf("%d\n", c);//9
printf("%d\n", a);//6
printf("%d\n", b);//10
return 0;
}
宏和函数对比
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
//宏的形式
#define MAX(x,y) ((x)>(y)?(x):(y))
//函数的形式
int Max(int x,int y)
{
return x > y ? x : y;
}
int main()
{
int a = 0;
int b = 20;
int c = 0;
c = MAX(a, b);
c = MAX(a, b);
return 0;
}
那为什么不用函数来完成这个任务?
原因有二:
1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
所以宏比函数在程序的规模和速度方面更胜一筹 。
2.更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于> 来比较的类型。
宏是类型无关的 。
**宏的缺点:**当然和函数相比宏也有劣势的地方:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏是没法调试的。
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
//使用
int* p2 = MALLOC(10, int);//类型作为参数
//预处理器替换之后:
int* p = (int*)malooc(10 * sizeof(int));
//int* p2 = (int*)malloc(10 * sizeof(int));
return 0;
}
宏和函数的一个对比
属 性 | #define 定义宏 | 函数 |
代 码 长 度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 |
执 行 速 度 | 更快 | 存在函数的调用和返回的额外开销,所以相对慢一些 |
操 作 符 优 先 级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。 |
带 有 副 作 用 的 参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一次,结果更容易控制。 |
参 数 类 型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是 不同的。 |
调 试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递 归 | 宏是不能递归的 | 函数是可以递归的 |
收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一次,结果更容易控制。 |
| 参 数 类 型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是 不同的。 |
| 调 试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
| 递 归 | 宏是不能递归的 | 函数是可以递归的 |
收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
[外链图片转存中…(img-RLphEeWV-1715865561483)]
[外链图片转存中…(img-LZnBptFR-1715865561483)]
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!