C语言参数传递方式
值传递(swap1函数) 地址/指针传递(swap2函数)
void swap1(int,int); //值传递 void swap2(int *p1,int *p2); //地址传递
gcc编译过程
gcc编译过程分为4个阶段:预处理、编译、汇编、链接。 预处理:头?件包含、宏替换、条件编译、删除注释 编译:主要进?词法、语法、语义分析等,检查?误后将预处理好的?件编译成汇编?件。 汇编:将汇编?件转换成 ?进制?标?件 链接:将项?中的各个?进制?件+所需的库+启动代码链接成可执??件
各种指针
NULL指针 NULL用于指示指针未指向有效位置。理想情况下,如果在声明时不知道指针的值,则应将指针初始化为NULL。 当由它指向的内存在程序中间被释放时,我们应该使指针为NULL。
悬空指针 悬空指针是没有指向正确内存位置的指针。当删除或释放对象时,如果不修改指针的值或者不置为NULL,就会出现悬空指针。 这时这个指针指向的内存可能被分配给了其他变量就会造成错误。所以是比较危险的。
野指针 就是只声明没有被初始化过的指针,他可能指向任何内存。
变量/函数的声明和定义
变量/函数的声明仅声明变量/函数存在于程序中的某个位置也就是后面程序会知道这个函数或者变量的类型,但不分配内存。 关于定义,当我们定义变量/函数时,除了声明的作用外,它还为该变量/函数分配内存。
#include<> 与#include ""的区别
include<>到系统指定?录寻找头?件,#include ""先到项?所在?录寻找头?件,如果没有找再到系统指定的?录下寻找。
ifndef/define/endif 的作用
防止头文件被重复包含和编译。 头文件重复包含会增大程序大小,重复编译增加编译时间。
内联函数和普通函数的区别
-
普通函数在编译后会被放到代码段,然后函数执行过程中调用普通函数是需要先压栈,然后根据函数调用地址调用函数,函数返回后执行出栈操作。这样就会存在一个调用过程,有时间消耗。 -
内联函数是代码被插入到调用者代码处的函数。如同 #define 宏,内联函数通过避免被调用的开销来提高执行效率。 -
内联函数和宏很类似,而区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。
宏定义define
宏定义又称为宏代换、宏替换,简称“宏”。
定义数据类型:#define reg register 定义数值:#define MAX 1000 定义函数:#define MAX(a, b) ((a)>(b)?(a):(b)) 用于简单表达式函数,处理更快,但是无法调试。
与typedef区别
typedef一般是定义数据类型。 typedef是在编译器阶段处理,define是在预处理器阶段处理。
与内联函数区别
内联函数在编译时展开,宏在预处理时展开; 内联函数直接嵌入到目标代码中,宏是简单的做文本替换; 内联函数有类型检测、语法判断等功能,而宏没有; inline函数是函数,宏不是。
c语?中有符号和?符号的区别
有符号:数据的最?位为符号位,0表示正数,1表示负数 ?符号:数据的最?位不是符号位,?是数据的?部分,只有正数,所以范围更大
指针与指针变量
指针:内存中每?个字节都会分配编号,这个编号就是地址, ?指针就是内存单元的编号。一个变量的地址就称为该变量的指针他保存的是一个地址。 指针变量:c语言有很多种变量,每种变量都会储存一种数据,而指针变量就是专门来储存指针的变量,本质是变量 只是该变量存放的是空间的地址编号。
int *p; p=&a; int *p就是指针变量, 对a取地址,p就是一个指针用来保存地址。
C语言的内存分区
C语言开发对内存使用有区域划分,分别是栈区(stack)、堆区(heap)、BSS、数据段(data)、代码段(text)。
BSS段:未初始化全局变量,未初始化全局静态变量, 数据段:已初始化全局变量、已初始化全局静态变量、局部静态变量、常量数据, 代码段:可执行代码、字符串常量。
结构体与共?体(联合体)的区别
结构体中的成员拥有独?的空间,共?体的成员共享同?块空间,但是每个共?体成员能访问共?区的空间??是由成员?身的类型决定。
共用体使用覆盖技术,成员变量相互覆盖。
关键字const
意味着只读。防止被修饰的成员的内容被改变。明确的告诉使用者不要修改这个变量。
在函数声明时修饰参数,表示在函数访问时参数(包括指针和实参)的值不会发生变化。
对于指针而言,可以指定指针本身为const,也可以指定指针所指的数据为const,const修饰的都是后面的值。
extern关键字
可以修饰全局变量或者函数,用于跨文件调用。
举例来说,如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。能够被其他模块以extern修饰符引用到的变量通常是全局变量。还有很重要的一点是,extern int v可以放在a.c中的任何地方,比如你可以在a.c中的函数fun定义的开头处声明extern int v,然后就可以引用到变量v了,只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。
数组特点
同?个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的。 不初始化:如果是局部数组 数组元素的内容随机 ,如果是全局数组,数组的元素内容自动赋为0。 完全初始化:如果?个数组全部初始化 ,可以省略元素的个数,数组的??由初始化的个数确定。
数组名作为类型:代表的是整个数组的??; 数组名作为地址:代表的是数组?元素的地址; 对数组名取地址:代表的是数组的?地址。
数组首地址与数组首元素地址
char arr[]={‘1’,‘2’,‘3’,‘4’,‘5’,‘6’};
char * b = arr;
arr
*arr、arr[0]、*(arr+0)
&arr
arr+1
arr[1]、*(arr+1)
函数的调用
int Add(int x,int y)
{
int sum = 0;
sum = x + y;
return sum;
}
int main ()
{
int a = 10;
int b = 12;
int ret = 0;
ret = Add(a,b);
return 0;
}
- 参数拷贝(参数实例化)分配内存。
- 保存当前指令的下一条指令,并跳转到被调函数。 //前两步在main函数中进行
- 移动ebp、esp形成新的栈帧结构。
- 压栈(push)形成临时变量并执行相关操作。
在一个栈中,依据函数调用关系,发起调用的函数(caller)的栈帧在下面(高地址方向),被调用的函数的栈帧在上面。 每发生一次函数调用,便产生一个新的栈帧,当一个函数返回时,这个函数所对应的栈帧被清除(eliminated)。 - return一个值。
- 出栈(pop)。
- 恢复main函数的栈帧结构。(pop )
- 返回main函数。
总结起来整个过程就三步: 1)根据调用的函数名找到函数入口; 2)在栈中申请调用函数中的参数及函数体内定义的变量的内存空间; 3)函数执行完后,释放函数在栈中的申请的参数和变量的空间,最后返回值(如果有的话)。
节省内存
结构体中使用bit位
根据数值大小来取位数
struct{
uchar a:1;
uchar b:2;
uchar c:3;
uchar d:2;
} One_bit
上述结构体占1个字节,免得单独定义a,b,c,d会总共占4个字节。
字节对齐
-
结构体(struct)的数据成员,第一个数据成员存放的地址为结构体变量偏移量为0的地址处。 -
其他结构体成员自身对齐时,存放的地址为min{有效对齐值为自身对齐值,指定对齐值} 的最小整数倍的地址处。 自身对齐值:结构体变量里每个成员的自身大小。 指定对齐值:有宏 #pragma pack(N) 指定的值,这里面的 N一定是2的幂次方。如1,2,4,8,16等。如果没有通过宏那么在32位Linux主机上默认指定对齐值为4,64位的默认对齐值为8,AMR CPU默认指定对齐值为8。 有效对齐值:结构体成员自身对齐时有效对齐值为自身对齐值与指定对齐值中较小的一个。
总体对齐时,字节大小是min{所有成员中自身对齐值最大的,指定对齐值} 的整数倍.
#pragma pack(N) 每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
———— 2022/08/03 家中
|