【篇一】C语言涵盖技术点整理清单

针对C这门语言,本人整理的觉得需要做笔记和理解的部分

整理目录如下

  • 语言的精髓-指针、二重指针
  • 数组、函数、函数库、数据指针、函数指针、预处理
  • 结构体、共用体、内存对齐
  • 关键字 auto、static、extern、const、define、typedef、inline、main
  • 内存、代码段、数据段、bss段
  • 位运算
  • offsetof、container_of、大小端模式
  • 存储类、作用域、生命周期、链接属性

语言的精髓-指针、二重指针

 >>使用说明   
    int a =111;  //a 代表变量a本身
    int *p;     //p 代表指针变量p本身, *p 说明定义一个指针
    p=&a;      //&a 代表变量a的地址值
    *p =22;   //*p 代表指针变量p所指向的那个变量,也就是变量a 

  注意点: 1:*p 只有在定义时标识指向其他变量的地址,其他情况用作指向变量本身
          2:*p 指针类型int 和所指向的变量类型需要一致  int *p =float b 错误 ,类型不匹配
          3:指针也叫做指针变量,和普通变量,数组没有本质区别,数据类型只是只是规定了所指向的变量的解析方式不同而已


void * : void指针,不确定的类型,程序员自己制定
    int a=12;   void * pVoid = &a;
       使用时自己强制转换,eg:     *(int *)pVoid  和 *(float *)pVoid;


NULL的含义
	定义:如果是c++(_cplusplus)环境, NULl的值为0, C环境,代表强制转换为void*的0,表示指向地址为0的地址,一般这个0地址操作系统都是不允许访问的
		#ifdef _cplusplus   #define NULL 0
		#else #define NULL (void *)0
		#endif

二重指针
      一重指针  char *p;
      二重指针  char ** p;
      解释:二重指针和一重指针本质上没区别,都是一个变量,意义上 二重指针指向的变量类型是指针类型,主要作用是让编译时做数据类型校验

      使用 char **p1 等价于 char *p2[5];
      解释:p1是一个二重指针,指向的是一个char型的指针, p2是一个指针数组,里面每个元素都是char*型的指针, 数组名右值时代表的是元素首地址,也就是第一个元素地址
           

数组、函数、函数库、数据指针、函数指针、预处理


>> 数组名: 做右值时,数组名表示数据的首元素地址  a = &a[0] = &a
   函数:函数名和数组名区别:函数名做右值时加不加&效果和意义都是一样的,但是数组名做右值时加不加一样不一样
    int a[3] ={2,3,4}
    int *p;

    p=&a;             //编译警告,但是执行正确 意义是数组的首元素的地址, incompatible pointer types initializing 'int *' with an expression of type 'int (*)[2]'
    p= &a[0]            //编译没警告,执行正确
    p= a                 // 编译没警告,执行正确,意义是整个数组的首地址

>>二维数组
    int a[5][6];
    数组名标识首元素的首地址 a=&a[0][0]
    int *p= &a[0]; 不可以
    int *p2 = a[0] = &a[0][0]; 可以
        使用:a[0][0] 的值为 *(p2+0)


>>指针数组和数组指正
    指针数组:一个数组内的每个元素都是指针变量的数组
    数组指针:一个指针,指向的是一个数据

    int * p[5]   //是一个指针数组
    int (* p)[5]  // 是一个数组指针
    void (* p)(void) //是一个函数指针
        赋值 p=pFunc;或者 p=&pFUnc;  调用 *p();
    int * (p[5])  //同 int * p[5]  ,括号可以没有,优先级[]最高

    扩展 char* ( * pFunc[10] ) (const char a)

    一般规律:先找核心(变量名p),如果核心和*结合,表示是指针,如果核心和[]结合,表示是数据,如果核心和()结合,表示是函数


代码示例
    定义:typedef int (*pFunc)(int a,int b);
        int add(int a,int b){
	        return a+b;
        }

    使用 pFunc func =add;
        int result =func(1,4);


>>预处理由源码到可执行程序过程
    1:源码.c ->(预处理)->预处理过的.c源文件 -> (编译) ->汇编文件.S ->(汇编) ->目标文件.o ->(链接) ——> elf可执行文件
    2: 此过程使用到的工具:预处理器,编译器,汇编器,连接器,再加另外一些工具,合起来就叫编译工具链,gcc就是一个编译工具链
    3:预处理的内容: 
        A:头文件包含:include,
            <>形式:系统提供的头文件,编译器会到指定目录下(编译期或操作系统配置)找,编译器还允许用 -I来附加指定其他的包含路径
            ""形式:程序员写的,如果当前目录没找到再导系统目录下找
        B:宏定义:define, 
        C:注释: 擦掉了所有的
        D:条件编译:宏是否定义 #ifdef NUM #else  #endif
                  或者 #if(条件表达式)
    4: 只预处理,不编译 gcc -E a.c
        只编译,不链接 gcc a.c -c   

>>函数库 glibc等
   
    静态库:建自己的函数库代码进行编译,不链接,形成.o的目标文件,然后用ar工具,将.o文件归档成.a的归档文件(又叫静态链接库文件),商业公司通过
        发布.a库文件和.h头文件提供静态库给客户使用,客户拿到通过.h得知函数的原型,然后进行 调用
    动态库:

    静态链接:将调用的函数的代码直接包含在当前代码位置,特点:执行时不依赖外部代码
    动态链接:不包含,在调用函数时,去指定的函数库中加载并调用

    总结:1 gcc编译默认使用动态库的,如果要使用静态库,需要命令加参数 -static 来强制静态链接
         2  有些库函数链接时需要额外使用 -lxxxx 指定链接
         3  如果是动态库,需要使用 -L 指定动态库的地址

>>数学库函数 math.h
	ubuntu位置:/user/include/i386-lunux-gnu/bits/mathcalls.h

>>ldd可以看文件用到哪些函数  ldd a.out
>>nm可以看归档文件用到哪些文件和函数  nm a.a

>>静态链接库制作和使用,扩展名 .a
	gcc  a.c -o  zg.o -c
	ar -rc libzg.a zg.o
	说明:首先使用 gcc -c 编译不链接,生成 zg.o文件,然后使用ar工具进行打包成(-rc创建文件) .a的归档文件
		库名 libzg命名规范  lib+库名, 使用时库名就可以
	注意:制作出来静态库后。发布时需要发布.a和.h文件

    使用: gcc b.c -lzg -L.
     -L:告诉链接器寻找库地址  .表示当前路径


>>动态链接库制作和使用(扩展名.so 对应的windows的.dll文件)的制作和使用
    编译 gcc  a.c -o  zg.o -c -fPIC
	链接 gcc -o libzg.so zg.o -shared

    发布时文件为  .so 和.h
    -fPIC 位置无关码,因为是操作系统函数,所以用时位置不确定,不能指定路径  
     -shared 共享库的形式链接

   使用动态链接库
    添加库到指定路径
        办法一:复制.so文件到 /user/lib 
        办法二: 使用环境变量 LD_LIBRARY_PATH, 将 .so文件目录导出到该环境变量
    gcc test.o  -lzg -L.

结构体、共用体、内存对齐

结构体:(struct)
    1:结构体定义的是一种新的组合数据类型,而不是变量,本身是不消耗内存的

    初始化: 1: 使用括号完全初始化     eg: {'张三',18}形式
            2: 使用点部分初始化       eg: {.username ='张三', .age=18}

公用体:(union)
    1:和结构体类型,不同点多个变量同时指向一个变量,里面实际只有一个参数,再使用时,只能选择某一个变量赋值和使用
    2:结构体占用字节数为 当前结构体中最大的某个元素的字节数
    3:不存在对齐问题,因为只有一个空间

struct user{
	int age=12;  //访问 user.age 实际为  int * p =(int *)&usr;; *p = 12
	double amount=5.4; //访问 user.amount 实质为  double *p =(double*)((int*)&usr + 1; ; *p = 12								
}

对齐访问,
	可以使用pragma_pack(n) (n=1/2/3/4/), 已pragma_pack()结束,区间的代码就表示使用该对齐方式
	或在需要使用对齐的类型后加__attribute__((packed))
	或在需要使用对齐的类型后加__attribute__((alligned(n))),此时是真个结构体已n字节对齐,不是单独变量
	原因:
	1:配合硬件,由于物理上的限制,使用对齐访问会提高效率
	2:其他硬件的一些内存依赖特性

    规则:
    1: 确定对齐的值,由编译器决定,一般是4字节
    2:地址开始位置需要是偶数位置
    3:当前变量的对齐是否结束(完成4字节)是由下一个变量决定的
    4:整个结构体是4的整数倍

关键字 auto、static、extern、const、define、typedef、inline、main

main: 有父进(命令行,shell)程执行和fork新进程,可以通过shell脚本的 $? 获取返回结果
	 argc,argv,参数
	 	argc:传递的参数个数,
	 	argv:传递的参数数组列表

auto: 普通局部变量
	1: 直接定义,可省略 int a = auto int a;

register: 寄存器形式的局部变量
	1:表现形式和auto一样,特点是c语言中最快的变量,c语言运行时会将该类型变量放在寄存器中运行

extern: 申明变量
	1:同函数的申明,告诉编译器进行链接,进行申明另一文件的全局变量,进行引用,申明时不能初始化

static: 
	1:  静态局部变量:修饰函数类变量,作用范围函数类,在函数初次调用时初始化,下次调用不销毁,已经使用上次的结果
	2: 静态全局变量:作用范围当前文件,只能本文件其他函数都提供使用,主要是链接属性上的不同

inline:内联函数
	1:函数前面加inline,本质上是函数,同时也有宏的有点,在预编译时代码原地展开,作用仅仅是减少调用函数时的开销

volatitle: 可变的. 修饰后会影响运行效率,
	1:修改变量,表示该变量可以被编译器之外的因素改变。非代码改变,比如多线程引用的变量,硬件会改变的变量,终端处理程序ISR引用的变量,
	2:编译器不会对该类型变量的访问进行优化

restrict:
	1; c99支持,只用来修饰指针,告诉编译器,修饰的该指针在访问时只能通过该指针访问
	eg: int f(int *restrict x, in b){
            *x =0;
			b=22;
			
			//return  *x;  没有加restrict,编译器编译后代码
			//return  0;  加了restrict,编译器编译后的代码
	     }
		 
常量: 三种定义方式   #define N 1
                  使用const 关键字
                  使用 枚举 enum
		const:
			1 const int *p;  p是一个指针,p所指向的一个int型的数据,p所指向的是一个常量
			2 int const *p;  p是一个指针,p所指向的一个int型的数据,p所指向的是一个常量
			3: int * const p  :p是一个指针,指向int型的数据,p指针本身是场量
			4: const int * const p  :p是一个指针,指向int型的数据,p指针和p指向的都是常量
			5:undef 使用  #undef  N,作用可以将刚才定义的常量 N去掉
			判断依据: const和*的位置,  在*前针对的是指针指向的变量, 在*后针对的p指针本身

define和typedef区别
	define dfChar char * 
	typedef char *  tpChar
	使用  dfChar c1, c2 等价于  char * c1, char c2;
		 tpChar c1, c2 等价于  char * c1, char * c2; 

	使用typedef包装结构体
		结构体变量申明必须使用 struct User user;
		可以包装结构体简写  typedef User{
						ing age;
					}User, *PUser;  //定义一个结构体类型,和一个结构体指针类型 理解方式 ,装换为 * struct  User user;
		使用别名   User user;

	使用define初始化 #define initnode(x,y){x,y} 等价于 struct list_head foo; foo.x = x; foo.y = y;


const和typedef结合
	typedef int * pInt1;
	typedef const int * pInt2;
	结果:  const pInt1 p1 等同于  pInt const p1;  p1指针是const, p1指向的变量不是const
		    pInt2 p2 ; p2 指针不是const, p2指向的变量是const 

内存、代码段、数据段、bss段

内存单位:  位(1 bit),字节(8 bit),半字(一般16 bit),字(一半32 bit)
内存编址方法: 内存由一个个相同的格子构成,每个格子有自己固定的物理内存编号,cpu通过地址编号获取内存格子的内容,
			内存编号对应的每一个格子大小固定不变,为1字节
内存对齐: 

>>> malloc 和 free
	malloc 返回值为申请到的内存空间的首地址,是一个void*类型的
		int *p =(int *) malloc(10* sizeof(int));
			可以使用 * (p+1)指针方式访问
	malloc(0) 返回一个16字节的空间, 返回空间是gcc自己定义的,返回是按照块为单位的,比如返回5个,可能会范围16个字节空间	

>>>代码段,数据段,bss段 是 C语言所有代码所有变量和常量使用的内存,静态局部变量
	代码段(.data)只读数据段(.text):程序可执行的部分,函数叠加而成的
	数据段:(也称为静态区)程序中的数据(全局变量和常量),在main函数前就已经初始化了,在重定位期间完成的初始化
	BSS段:特点是被初始化为0,bss段也属于数据段,就是初始化为0的数据段
	文件映射区:系统打开文件后,文件内容映射到区里,以后就直接在内存操作文件内容,写完再从文件映射区写回磁盘
	内核映射区:操作系统内核程序映射

位运算

& 位与: a ==1 && b==1 为1,其他 0
| 位或 a ==1 || b==1 为 1 ,  其他 0
^ 异或 a !=b 为 1               其他 0
~位取反
<< >> 左右移位 , 第一位是从零开始的

代码实战 a=0xFFFFFFFF
    1:给定一个整数a,清楚a的bit7,保证其他位不变
         a & (~(  0b1<< 7  ))
    2:给定一个整数a,清楚a的bit5 ~bit7 ,保证其他位不变
         a & (~( 0b111<< 5))
    3:给定一个整数a,取出bit5 ~bit7得值(先把其他位清零,然后位右移)
         a= (a & 0b111<< 5 ) >>5;
    4:给定一个整数a,给bit5 ~bit7赋值为0b101 (清除5~7位,然后位或)
         a= (a & ~(0b111<< 5) ) || (0b101 <<5);
    5:使用宏定义,第n位置位  #define fc(x,n)  (x | (1<<n) )

 

offsetof、container_of、大小端模式

offsetof
	作用:计算结构体中某元素和结构体首地址的偏移量(实质是通过编译器来计算,虚拟一个变量)
	定义:#define offsetof(TYPE,MEMBER) ( (int) &( (TYPE*)0) ->MEMBER )
	原理:虚拟一个type类型结构体变量,然后用type.member的方式访问那个member元素,继而得到member相对于整个变量的首地址的偏移量
			(type *) 0 这是一个强制类型装换,把0地址强制类型转换成一个指针,这个指针指向一个结构体变量,(实际上这个结构体变量可能不存在,但是我们只要不去解引用就行)


container_of
	作用:知道某个元素的指针,推断出结构体的首地址
	定义:#define container_of(ptr,type,member)({
	 const typeof(((type*) 0) ->member) * _ptr=(ptr);
	 (type *)((char *) _mptr - offsetof(type,member));  
	})
	原理:1:typeof关键字的作用:typeof(a)时由变量a得到a的类型,typeof就是由变量名得到变量数据类型的
	     2:先用typeof得到member的元素类型,定义成一个指针,然后用这个指针减去改元素相对于真个结构体变量的偏移量,得到之后就是真个结构体的元素首地址了,然后把这个地址强制转换成type *即可

大小端模式
	大端模式(big endina) 和小端模式(litter endina)
	在串口通信,计算机存储中,一次只能发送一个字节,byte0,byte1,byte2,byte3, 那么,按照什么顺序保证发送和接收方的方式一致呢,这就是大小端问题
	大端:高位对应低地址,小段:高位对应高地址

	检测机器的大小端模式: 二进制层次
		  1:用union
		  2:指针方式 int a=1; char b= (char *) &a; b==1为小端
	注意:1:不可以通过位操作去校验大小端,
		 2:不可以通过移位操作  ,
		 3:强制类型转换,
		 原因:因为位与是编译器提供的,这个运算高于内存层次的,在计算时,编译器会做值的两边适配,高字节对应 高字节,低字节对应低字节	
		 C语言对运算符的级别是高于二进制层次的,右移运算符永远是将低字节移除,二和二进制存储时这个低字节还在高位还是低位无关的

 

存储类、作用域、生命周期、链接属性

链接属性:
    1:程序的生成过程 编译+链接,编译是为了将函数,变量编译为.o形式的二进制文件
        链接时为了将各个独立分开的二进制的函数链接起来形成一个整体的二进制可执行程序
    2:编译已文件为单位,链接一个工程为单位,编译器工作时将所有的源文件依次读进来,单个为文件进行编译的

外链接,内链接,无链接
    外链接:外部链接属性,比如函数,全部变量,可以跨文件访问
    内链接:当前文件范围, static修饰的函数/全局变量, 
    无链接:本身不参与链接,如auto的局部变量,inline,宏

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

源14

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

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

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

打赏作者

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

抵扣说明:

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

余额充值