C语言基础补充知识、易错知识

学习完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;
#define 是 C 语言中定义的语法,是预处理指令,在预处理时进行简单而机械的字符串替换,不作正确性检查,只有在编译已被展开的源程序时才会发现可能的错误并报错。
typedef 是关键字,在编译时处理,有类型检查功能。它在自己的作用域内给一个已经存在的类型一个别名,但不能在一个函数定义里面使用 typedef。
7.字符串常量
 char* p = "test";

这行代码声明了一个字符串常量,常量是不允许修改的,如果这个字符串可能需要被修改,应该用数组来初始化:

char strp[]="test";

8.For( ; ; )和 while(1)区别

两者上层功能一样,但底层 for (;;)指令少,不占用寄存器,而且没有判断跳转,比 while
(1)好。
9.数组名自加
        数组名代表的数组的首个元素的地址,是一个地址常量,比如一个数组s[2],s++这样是错误的,应该写成s+1这种形式。
10.
10.用乘法代替除法提高效率
尽量用乘法或其它方法代替除法,特别是浮点运算中的除法,浮点运算除法要占用较多 CPU 资源。
11.sizeof 和 strlen 区别
sizeof 是关键字,不是函数,strlen 只能用于字符串类型, sizeof 测一个指针长度,为 4 字节,sizeof 包含字符串中\0 的长度,sizeof 测字符串比 strlen 多 1。
12.malloc失败
        malloc失败返回值为NULL,有可能是申请空间过大,还有一种可能是之前申请了空间,在初始化时越界了,覆盖到了下一个空闲块的头节点,从而破坏了 malloc 管理的环形链表,malloc
就无法从一个空闲块的指针字段找到下一个空闲块了。
13.二维数组
         二维数组 在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”。

 

#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)栈区(stack segment):由编译器自动分配释放,存放函数的参数的值,局部变量的值等。在 Windows 下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS 下,栈的大小是 2M(也有的是 1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示 overflow。因此,能从栈获得的空间较小。
2)堆区(heap segment) : 一般由程序员分配释放,若程序员不释放,程序结束时可能由系统回收 。它与数据结构中的堆是两回事。堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
3)全局区(静态区)(data segment):全局变量和静态变量的存储区域是在一起的,程序结束后由系统释放。数据区的大小由系统限定,一般很大。
4)文字常量区:常量字符串就是放在这里的, 程序结束后由系统释放。
5)程序代码区:存放函数体的二进制代码。

存取方式:
栈:先进后出,像箱子
堆:存取是随意,这就如同我们在图书馆的书架上取书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,书架这种机制不同于箱子,我们可以直接取出我们想要的书。
18.volatile
        
        防止编译器优化。什么是编译器优化呢:代码为了更高效率,会把某些经常用到的值放在寄存器中,每次拿这个值就拿寄存器中的值,而不是拿内存地址中的值,如果这个值不小心改变了,还拿寄存器的值就会出问题。
        volatile译为:易变的。在变量前加上该关键字修饰,是告诉编译器,这个变量是一个容易改变的变量,不要对它进行优化,每次都要到变量的地址中去读取变量的数据。
        几个常用场景:
        并行设备的硬件寄存器;多线程任务重被多个任务共享的变量;中断程序中修改的供其他程序检测的变量。
19. __attribute( )作用
        
        __attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。 __attribute__书写特征是:__attribute__前后都有两个下划线,并切后面会紧跟一对园括弧,括弧里 面是相应的__attribute__参数。
        
        __attribute__语法格式为: __attribute__ ((attribute-list)) 其位置约束为: 放于声明的尾部“;”之前。
         __attribute__((packed)):
        __attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是 GCC 特有的语法。这个功能是跟操作系统没关系,跟编译器有关,gcc 编译器不是紧凑模式的。
​​​​​​​
在 GCC 下:struct my{ char ch; int a;} sizeof(int)=4;sizeof(my)=8;(非紧凑模式)
在 GCC 下:struct my{ char ch; int a;}__attrubte__ ((packed)) sizeof(int)=4;sizeof(my)=5
20.大小端
小端:字节的高位存放在地址的低位。
​​​​​​​大端:字节的高位存放在地址的高位。
21.static关键字

 1)静态全局变量

        使用:全局变量前加static,修饰全局变量为静态全局变量。

        作用:改变全局变量的可见性。静态全局变量的存储位置在静态存储区,未被初始化的静态全局变量会被自动初始化为0。静态全局变量在声明它的文件之外是不可见的,仅在从定义该变量的开始位置到文件结尾可见。

  2)静态局部变量

       使用:局部变量前加static,修饰局部变量为静态局部变量。

       作用:改变局部变量的销毁时期。静态局部变量的作用域和局部变量的作用域一样,当定义它的函数或语句块结束的时候,作用域结束。不同的是,静态局部变量存储在静态存储区,当静态局部变量离开作用域后,并没有被销毁。当该函数再次被调用的时候,该变量的值为上次函数调用结束时的值。

  3)静态函数

        使用:函数返回类型前加static,修饰函数为静态函数。

        作用:改变函数的可见性。函数的定义和声明在默认情况下都是extern的,但静态函数只在声明它的文件中可见,不能被其他文件使用。
 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我的未来不是梦嘻嘻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值