学习完C语言基础后,还远远不够,记录整个学习过程中遇到的其他经常遇到、易错的C语言知识,慢慢夯实自己,水平有限,如有不对,请指正。
1.预编译指令
预编译指令,包括 #define、#undef、#ifdef、#ifndef、#if、#elif、#else、#endif、defined。
#define | 定义一个预处理宏(只是机械的替代) |
#undef | 取消宏的定义 |
#if | 编译预处理中的条件命令,相当于C语法中的if语句 |
#ifdef | 判断某个宏是否被定义,若已定义,执行随后的语句 |
#ifndef | 与#ifdef相反,判断某个宏是否未被定义 |
#elif | 若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if |
#else | 与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else |
defined | 与#if, #elif配合使用,判断某个宏是否被定义 |
2.Linux下代码的运行过程
gcc从.c文件生成可执行文件的过程主要是:预处理、编译、汇编、链接
预处理:
预编译阶段把.cpp和.h等文件编译成一个 .i文件,第一步预编译的过程相当于如下命令:
gcc -E hello.c -o hello.i #-E表示只进行预编译,-o指定生成文件名
预编译阶段主要处理那些源代码文件中以“#”开始的预编译指令:展开宏,引入头文件,删除注释,处理#ifdef等指令,添加行号和文件名标识,保留所有的#pragram编译器指令。
编译:
编译过程就是把预处理完的文件进行词法分析、语法分析、语义分析及优化后生成汇编代码文件,命令:
gcc -S hello.i -o hello.s
汇编:
汇编过程把汇编代码转成机器可执行的指令:
gcc -c hello.s -o hello.o
生成的是二进制文件,查看命令:
objdump -h hello.o
链接:
链接阶段主要是:合并各个段,符号解析、符号重定位
gcc hell.o -o hello
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个形成的文件可以被加载到内存中运行。
静态库:一般为 .a文件,作为链接器的输入。比如printf函数需要引用一个目标模块,通常将这些模块提前打包,链接器就把这个目标模块复制进程序中。利用静态函数库编译成的文件比较大,因为整个 函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即链接后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被复制进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。
动态库(共享库):一般为 .so文件, 相对于静态函数库,动态函数库在编译的时候 并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。
3.char 和 unsigned char
因为char只占一个字节,范围比较小,所以希望一个变量只是正值是,最好使用unsigned char,否则当变量的值越界时超过127(-128<=char<=127),其值已经完全不一样了。
4.解决函数返回局部变量指针
函数返回局部变量时实际上是返回变量值的拷贝。假设a为局部变量,在栈区存储,虽然在函数调用结束后所在内存会被释放回收掉,但返回值不是访问地址,而是a的拷贝副本,所以是可行的,但是如果返回的是指向a的指针,函数调用结束后所在内存会被释放回收掉,此时指针指向的方已经空了,已经找不到a了,所以这样是不可行的。(除非是静态局部变量以及堆上的数据,只要还没被手动释放)
5.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我 们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
回调函数实现的机制:
(1)定义一个回调函数;
(2)提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;
(3)当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。
6.typedef 和#define 的区别
#define INTPTR1 int*
typedef int* INTPTR2;
INTPTR1 p1, p2;
INTPTR2 p3, p4;
//声明一个指针变量 p1 和一个整型变量
//p2 声明两个指针变量 p3、p4
typedef int * pint;
#define PINT int *
Const pint p;//p 不可更改,p 指向的内容可以更改,相当于 int * const p;
Const PINT p;//p 可以更改,p 指向的内容不能更改,相当于 const int *p;或 int const *p;
char* p = "test";
这行代码声明了一个字符串常量,常量是不允许修改的,如果这个字符串可能需要被修改,应该用数组来初始化:
char strp[]="test";
8.For( ; ; )和 while(1)区别
#include "stdio.h"
int main()
{
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
printf("%d\n",**(a+1));
printf("%d\n",*(*(a+1)+1));
return 0;
}
14.malloc和calloc的区别
calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据
15.位域
C语言 | 位域的使用详解_嵌入式大杂烩的博客-CSDN博客_位域
16.#ifdef和#if defined的区别
#ifdef 和 #if defined 的区别在于,后者可以组成复杂的预编译条件,比如
#if defined (AAA) && defined (BBB)
xxxxxxxxx
#endif
#if defined (AAA) || VERSION > 12
xxxxxxxxx
#endif
而#ifdef 就不能用上面的用法,也就是说,当你要判断单个宏是否定义时
#ifdef 和 #if defined 效果是一样的,但是当你要判断复杂的条件时,只能用 #if
17.内存分区
1)静态全局变量
使用:全局变量前加static,修饰全局变量为静态全局变量。
作用:改变全局变量的可见性。静态全局变量的存储位置在静态存储区,未被初始化的静态全局变量会被自动初始化为0。静态全局变量在声明它的文件之外是不可见的,仅在从定义该变量的开始位置到文件结尾可见。
2)静态局部变量
使用:局部变量前加static,修饰局部变量为静态局部变量。
作用:改变局部变量的销毁时期。静态局部变量的作用域和局部变量的作用域一样,当定义它的函数或语句块结束的时候,作用域结束。不同的是,静态局部变量存储在静态存储区,当静态局部变量离开作用域后,并没有被销毁。当该函数再次被调用的时候,该变量的值为上次函数调用结束时的值。
3)静态函数
使用:函数返回类型前加static,修饰函数为静态函数。
作用:改变函数的可见性。函数的定义和声明在默认情况下都是extern的,但静态函数只在声明它的文件中可见,不能被其他文件使用。