这些是我以前学习《C和指针》做的一些笔记,主要是一些的小知识点。
1.C 语言文件的编译的4个阶段:
源代码(.c / .cpp文件)
-> (1.) 预编译阶段(操作符 gcc -E)。
预处理器在源代码上执行一些文本操作。例如,用实际值代替由#define指令定义的符号以及读入由#include指令包含的文件内容。
-> (2.)编译阶段(操作符 gcc -S)。
源代码经过解析,判断它的意思。这个阶段是产生绝大多数错误和警告信息的地方。编译器优化程序并最后生成汇编级指令(此阶段产生文件名后缀为 .s)。
-> (3.)汇编阶段(操作符 gcc -C)。
汇编文件经过汇编器的处理,将文件中的汇编级指令转化成包含机器指令的目标文件。这一阶段只把现有的代码转换成机器代码,而像printf()这样的函数调用暂不解析。这阶段的机器级文件打开不可读。(这一阶段产生的目标文件名后缀为 .o)。
-> (4.)链接阶段(操作符 gcc)。
这是最后的阶段,是将目标文件和其他目标代码(多文件编译的话)以及库文件进行链接。所以printf()函数在库文件中解析翻译,程序才知道这个函数到底输出什么(之前只是简单的采用占位符)。
编译器也做一些额外的操作:它把一些程序开始运行和程序结束运行时所需的附加代码合并到程序中。这样产生最后的可执行文件既二进制文件。
2 C 语言文件的执行的4个阶段:
(1.)首先,程序必须载入内存中,这个任务由操作系统完成。 那些不是储存在堆栈中的尚未初始化的变量将在这个时候得到初值。
(2.)执行开始,通常一个小型的启动程序与程序链接起来,它负责处理一系列日常任务,如收集命名行参数以便程序能够访问它,等等。
(3.)调用main(),开始执行程序代码。在绝大数机器中,程序将使用一个运行时的堆栈,它用于储存函数的局部变量和返回地址。
(4.)程序终止。终止的产生原因有很多,可能是正常终止,既main()返回。也有可能是在执行过程中产生错误自行终止,比如core dump 。也有可能是用户按下break自行退出。
3.输出表示:
十进制 %d ; 八进制 %o ; 十六进制 %x ; 浮点数 %f ; 字符 %c ; 字符串 %s ; 换行 %n ;
4.array[] 和 *array
当数组名作为实参时,传给函数的实际是一个指向数组起始位置的指针,也就是数组在内存的物理地址。这个时候可以看做数组名与指针等同。
5.const :变常量。
(1.)做参数时 它声明该函数的作者意图是这个参数不可以被修改。
(2.)等修改时,编译器去验证是否违背了该意图。
6.常用的字符串函数
strncpy(a,b,n); a : 目标字符串 b : 源字符串 c : 复制的大小。
getchar ; 输入字符。
putchar; 输出字符。
strcpy(a,b); 把b复制到a的位置,覆盖a的原有值。
strcat(a,b); 将b添加a的末尾。
strchr(str,a); 在字符串str中搜索a,返回第一次出现的位置。
7.static
局部变量和返回地址存在于 堆栈 中。
static 静态变量 一次初始化,存在于静态区,生命周期大于函数。
8.代码书写格式
(1.)空行用于分隔开不同的逻辑代码段,按功能分段。
(2.)操作符与操作数之间用空格隔开,表达式复杂时可以省略。
(3.)嵌套语句要逐层缩进4格。
(4.)返回类型独占一行。
9.类型范围
int/char -> 0~127
unsigned char -> 0~255
short int = int -> -32767~32767
long int -> -2147483647~2147483674
10.enum
枚举类型。它的值为符号常量而不是字面类型
{a, b, c = 0, d} a:1 b:2 c:0 d:1
11.NULL和NUL
NULL : 是在< stddef.f>头文件中专门为空指针定义的一个宏。
NUL :是ASCLL字符集中第一个字符的名称,对应一个0值(‘\0’),C语言中没有NUL这样的预定义宏。
12.typedef和#define
typedef : 它允许你为各种数据类型定义新名字。
#define:相比typedef,无法正确的处理指针类型。
**13.变量的储存类型**SCLL字符集中第一个字符的名称,对应一个0值(‘\0’),C语言中没有NUL这样的预定义宏。
指存储变量值得内存类型,有三个地方储存,分别是普通内存,运行时的堆栈,寄存器。
凡任何代码之外申明的变量总是储存于静态内存,也就是不属于堆栈的内存,这类变量称为静态变量。
14.移位
(1.)逻辑移位,左边移入的位用0填充。
(2.)算术移位,左边移入的位由原来该值得符号位来决定,符号位为1,则移入位均为1,符号位为0,则移入位均为0。
15.”,”
逗号操作符 “,”用于将多个表达式分开,使之便于维护。
16. 指针
指针的初始化是用&操作完成的,用于产生操作数的内存地址。
如果变量是静态的,它会被初始化为0,若是自动的,不会初始化。如果你已经直到了指针将指向哪里地址,就把指针初始化为该地址,否则,把它初始化为NULL。
17.定义和声明
函数先声明,在定义。声明是向系统申请空间,并把函数名,参数的数量和类型,返回值传给编译器核对。声明了定义时系统核对参数列表及返回值等 是否匹配,定义不需要分配空间。
18.# 与 ##
单个#代表的是宏,##的作用是将两段字符串连接起来。
19.指针调整
当一个指针和整型数据执行算术运算时时,指针会根据指针所指的类型进行调整大小。比如 数组名加3是指,这个指针指向加3个指针所指类型 后的位置。
20.数组越界
指针指向数组最后一个元素的后面的空间是合法的,但对这个空间进行间接访问或操作就是非法的!这就是数组越界。
另,当数组是无符号类型时,指向a[0]前面的数据也是非法的。
unsigned length = 100;
for(length; length >= 0; length--) //错误,length==0时,会length--,unsigned没有负数,循环条件出错,不会产生[-1]退出。
{}
21.递归的两个特性
(1.)存在限制条件,
(2.)每次递归调用后越来越接近限制条件。
每次递归调用参数压栈,为局部变量分配内存空间,当递归调用函数的每次调用返回时,上述操作出栈还原。
22.数组名与指针的区别
数组名的值是一个指针常量,也就是元素的的第一个地址。
(1.)数组具有确定数量的元素,而指针只是一个标量值。
(2.)只有当数组名作为函数表达式的参数出现时,编译器才将它改写成所指向的地址(即指针)
(3.)当数组名作为sizeof操作符时,返回的是数组的长度,而不是指向数组的指针的长度。sizeof测试指针,永远是指针的大小(大小跟操作系统有关)。
或单目操作符&的操作数时,返回的是一个数组名的地址所产生的指向数组的指针,而不是指向指针的指针。
23.函数中数组名和指针的效率问题
(1.)当你根据某个固定数目的增量在一个数组中移动时,比如for循环,指针变量比使用下标产生效率更高的代码。
(2.)声明为寄存器变量通常比位于静态内存和堆栈中的指针更有效率。
(3.)那些必须在运行时求值得表达式较之诸如&array[size]或array+size这样的表达式往往效率代价更大。
24.内存对齐
内存对齐时计算机的内存存储模式,有以下几个特点。
(1.)所有行的内存大小与类型最大的大小等齐。
(2.)所有行左边界对齐。
(3.)不能内存溢出。
int a;
char b;
int c;
float d;
char e;
内存大小为? 4(int) + 1(char) +3(对齐) +4(int) + 4(对齐) + 8(float) + 1(char) = 24 bytes; (每行大小与float对齐为8,不溢出不用换行存储,否则对齐换行存储)
25.申请空间
malloc : 从内存中提取适合的内存并向程序返回一个指针指向这块内存,但并没有进行初始化。内存不足时,返回NULL指针,返回的指针要进行检查,是否分配空间失败。
calloc 也用于分配内存,返回指向内存的额=指针,并将内存初始化为0;
realloc 用于修改一个原先已经分配的内存大小。
free 传递给free必须是一个从malloc, calloc,realloc函数返回的指针。不允许释放一快内存的一部分;不允许二次释放已经被释放过的空间。
内存泄露 :是指动态分配后,当它不再使用时没被释放,这样会增加程序的体积,有可能导致程序崩溃。