工作五年C语言心得整理

工作五年C语言知识点整理

1 const

1.1 const 在*左边

const 在*左边, 比如const int *p 或 int const *p,指针指向的值的内容不能变,即p可以任意指向一地址p=&a,但是*p无法更改;

举例,以下函数,防止入参num被子函数修改。

cJson_CreatIntArray( const int * num, int count);

(2019.2.14)

1.2 const 在*右边

const 在*右边,比如 int * const p3=&a,指针地址不能变,所以必须初始化,*p3可以任意赋值。

(记忆方法:*p是否为整体,若是所代表的值不能被更改;若不是,指针必须初始化,不能另指)

1.3 const修饰返回值

const修饰返回值时,其返回值内容不能被修改,只能复制给加const修饰的同类型指针。

const char *str = cJson_GetErrorPtr();

2 static

2.1 修饰函数局部变量

修饰函数局部变量,开辟内存;

2.2 修饰全局函数和全局变量

修饰全局函数和全局变量——》》只能在本源文件使用,即便extern外部声明也不行;

3 指针

3.1 双重指针

char **p = "123";

上记表达式的解释如下:

地址
p = 0x81 *p = *(0x81) = 0x82
*p = 0x82 *(*p) = *(0x82) = “123”

3.2 函数指针

以下是函数指针应用的一个例子,注意TBL1和TBL_p是两个层次上的数组,合起来其实是一个二维数组。

typedef struct{
	int num;
	int* fun(void*); /*结构体中的函数指针,即函数名,其代表函数的入口地址*/
}STR;

STR TBL1[] =  /*结构体数组,数组名本身是指针*/
	{1, FUN1},
	{2, FUN2},
	{0, NULL}, /*此行末尾可以加逗号,但不建议*/
	};

STR TBL2[] =  
	{1, FUN3},
	{2, FUN1},
	{0, NULL}, 
	};
	
STR* TBL_p[] = {TBL1,TBL2,NULL}; /*结构体数组名(指针)作为成员的一维指针数组*/

RunTbl_p = TBL_p[0];

ret = RunCtrl(RunTbl_p); /*此函数逻辑上对TBL1上的函数依次执行,也可根据执行情况,跳着执行*/

(2020.5.21)

4 字符char

4.1 “字符型数据类型”和“字符”

注意“字符型数据类型”和“字符”的区别,char 是可以存储数字的;

字符型char是整数类型,只是为了方便处理字符,通过ASCII码用特定整数表示特定字符。

例如

char a = 48;

对于内存而言是相同的,输出使编译器会选择相应形式。

输出形式
%d 48
%c ‘0’

(2019.3)

4.2 常用字符ASCII码

字符 ASCII
空字符 ‘\0’ 0
空格字符 32
‘0’ 48

4.3 字符串

C 语言中并不存在字符串这个数据类型,而是把字符串当作数组来处理的,比如char *a=“hello”;a[1]=“e”;

换句话说,字符串属于char类型的数组。并且,一定要‘\0’结束,即使空字符串也是。

=>即字符串是以‘\0’结尾的特殊的char类型数组。

字符串赋值与比较要用到库函数。(初始化除外)

(2019.4.15)

4.4 NULL

NULL (void *0)值为0,其实是0地址,C语言中不允许被访问。

C程序中 NULL = ‘\0’ 为真,只是两者数值正好相同,但两者本质不同。

(2019.7.5)

5 类型长度

5.1 sizeof

操作符sizeof(int) 和 sizeof( i )均合法。

注意对于计算字符串长度sizeof和strelen的区别:

char ss[] = "abcd";//以‘\0’结尾的特殊的char类型数组=>字符串
int len1 = sizeof(ss);//为4+1=5
int len2 = strlen(ss);//4
char ss2[3] = "";//char类型数组
int len3 = sizeof(ss2);//3,注意不是字符串,所以无需加‘\0’这个字符
int len4 = strlen(ss2);//0,strlen专注于字符

(2022.11.30)

5.2 64位macine

在64位机器中各类型的byte长度如下:

类型 字节长度
short 2
int / long 4
float 4
double 8
void * 8

(2020.10)

5.3 float

注意计算机单精度浮点数float在内存中以科学计数法的形式储存,与int,char本质上不同。

6 C语言内存模型

high address
stack 局部变量,函数参数
heap 动态内存分配(malloc,free)
BSS 存放未初始化的全局变量与静态变量
Data 存放初始化后的全局变量和静态变量
文字常量区 常量字符串
Text 代码
low

注意点1:stack和heap相向延申;

注意点2:常量字符串存放与文字常量区,程序结束时系统释放,比如

strcpy(p1,"123456");

"123456"放在常量区,所有”123456“都会被编译器优化成一个地方。

7 数组与结构体

7.1 直接赋值问题

数组相较结构体是C语言中的二等公民,后者可以作为函数参数和返回值,前者不行,传参时退化成一个指针。

数组不能直接赋值,因为数组名是一个地址常量,常量不能被赋值。但是当一个数组是一个结构体成员时,可通过结构体之间的赋值间接达到数组整体复制的效果,即:

stru1.array = stru2.array;  /*非法*/
stru1 = stru2; /*合法*/

汇编上,结构体赋值,采用类似memcpy的形式,而不是逐字copy。(类似C++中的浅拷贝,有指针的情况下留意,free不当,容易出现野指针。)

(2019.8.2)

#include <stddef.h>
#include <stdio.h>


typedef struct{
    
    
       int s1;
       int s2;
}Struct;


int test()
{
    
    
	return 0;
}

int test2()
{
    
    
	return 0;
}
	
int main()
{
    
    
	int array[3] = {
    
    1,2,3};
	Struct stru = {
    
    1,2};
	Struct sru2 = stru;
	int a = 5;
    void* p = NULL;

	p = array;
	printf("%x\n", p);
	p = test;
	printf("%x\n", p);
	p = &stru;
	printf("%x\n", p);
	p = &a;
	printf("%x\n", p);

	return 0;
}	

如上述代码所表示的,结构体名跟普通变量名一样,需要通过&来取地址,其是变量;而数组名和函数名一样本身就代表地址,是地址常量,如同&stru2 = &stru非法一样,数组名之间也无法赋值,进一步说,array是常量,而array[0]则是变量,这也是array与&array[0]同样表示地址的原因。

7.2 字符串数组

注意数组指针与指针数组的区别:

char a[][10]; /*a为数组指针,不可变,储存一个地址(相当于2级指针)*/
char *a[]; /*指针数组,适用于指向若干字符串,字符串没有大小限制,编译器一般优化为连续储存*/

(另外,注意 *(a+1) 与 *(a[0]+1) 的区别,前者指a[1][0], 后者指a[0][1]

了解指针数组作为main函数形参的形式:

int main(int argc, char *argv[]); /* arguement count, arguement value */
int main(int argc, char **argv); /* *argv[] => argv[][] => **argv, */

一般来说,argv[0]被系统自动赋值为程序运行的全路径名,而argv[argc]为NULL;

(2019.9.18)

7.3 结构体的位域

C语言结构体的位域(bit-field)通过bit二进位的利用,来节省存储空间,例如:

struct bf{
	int a:4;  /* 类型一般定义为unsigned int,避免符号位的影响 */
	int :0;   /* 无位域名,只用于填充int类型余下28bit,不能使用 */
	int b:4;
	int c:2;
	int d:2;  /* 剩余32-8=24bit被自动填充 */
}             /* 此结构体总共两个int长度 */

注意的是,C语言中结构体存储是采用对齐的方式,提高访问效率,即变量的存放地址应该能够整除变量的字节数。比如double变量应该放在能被8整除的地址上。

(2019.7.18)

(2021.7.11)补充:关于结构体的alignment问题,以4整除单位,如下例,应该为24byte长。

struct exm{
	char a;
	int b;          //a与b之间有3byte dummy
	char c;
	double d;       //c与d之间有7byte dummy
}

8 define

8.1 关于C函数可变参数占位符...

printf的实现。

传递原理:函数参数是一块连续地址,理论上只要探测到其中一个参数地址与所有参数的类型,就可推测出其他参数的位置。

实现:需包含<stdarg.h>:va_start, va_list, va_arg, va_end. 因库函数实现机理,头个参数必须确定。传参结束机制需自己实现。

8.2 关于#define Dbg(...)

可以去除定义范围所有的Dbg函数。

并且,甚至可以不加参数,在消除Dbg函数基础上,还可以消除相关变量。

通常来说,Dbg形式如下:

#if defined(DBG_PRINT)            
#define DbgFDbg	DbgFPrintf2
#else
#define DbgFDbg(...)
#endif

在编译阶段,可以在gcc之后加参数-D DBG_PRINT来定义defined(DBG_PRINT)DbgFPrintf2就是实体打印函数,如果不加参数,就去除定义范围所有的Dbg函数,相当于一个debug开关作用。

(2021.8.23)

8.3 #define的用法

#if defined(DbgTxt)
#undef DbgTxt            取消宏标识符
#define DbgTxt(p1) __FILE__,(p1),0  
#endif

__FILE__,__TIME__等是编译器预定义宏,(p1)是参数。

大规模开发过程中,define最重要的作用就是条件编译。

#ifndef _test.h_
#define _test.h_
//中间各种定义
#endif

用来防止头文件重复编译。

注意define的作用域只限于当前C文件,而不是整个工程。

(2021.3.25)

9 汇编语言

9.1 C语言中嵌入汇编代码

C语言中嵌入汇编代码可以提高运行效率(现在的编译器优化得足够出色),实现C语言不具备的机器要用到的功能。GNU GCC( AT&T汇编语言 )格式如下:

int main()
{
    
    
	int  input,output,temp;
	input = 2;
	__asm(
		"movl %0,%%eax;\n\t"  /* %%eax,寄存器使用两个百分号 */
		"movl %%eax,%1;\n\t"  /* %1,占位符,表示使用寄存器  */
		"movl %2,%%eax;\n\t"
		"movl %%eax,%0;\n\t"      /* 到此处为汇编语言模板部分 */
		:"=m"(output),"=m"(temp)  /* 输出部分,m表示内存,not register */
		:"r"(input)               /* 输入部分 */
		:"eax");                  /* 毁坏部分,表示汇编执行过程中此寄存器会被改写,提前保护 */
		
	return 0;
}

9.2 volatile

在多线程环境下,某个变量可能被外部改写,不能将其缓存到寄存器,每次使用时需要重新获取。因为编译器没有线程概念,需要使用volatile来限制编译器进行优化。

9.3 函数调用栈结构

32位AT&T格式汇编语言可以如下生成:

gcc -o xx.s -S xx.c -m32

寄存器介绍:

ebp 栈底寄存器
esp 栈顶寄存器
eip 指令寄存器,指向代码。(存储地址)
eax 累加寄存器,常用于函数返回值
ecx 计数寄存器

主要指令有 pushl,popl,movl,call,ret…

函数调用栈结构如下:

高地址,栈底方向
调用者函数栈帧
返回地址
上一栈帧EBP
局部变量1 被调用函数的栈帧
局部变量2
低地址,栈顶方向

(2019.9.19)

10 内存溢出问题

例如如下函数:

strcpy(char *dest,const char *src,int n);

当n>dest串长度时,dest栈空间溢出产生崩溃异常,即出bug,堆栈缓冲区溢出有时可以被系统检测到以产生终止该过程的segmentation fault。

C/C++中容易造成缓冲区溢出的函数:strcpy(),gets(),strcat()…数组下标越界,或打印字符串时无终止符。许多病毒就是利用缓冲区溢出漏洞对操作系统进行攻击的。

(2019.6.17)

11 运算符优先级问题

一般来说,遵循加括号原则。

但偶尔也有注意不到的时候,比如遇到下例情况:

//*pp_edtinf->lteSjtCom.sjtkind = ( UCHAR )subject;
(*pp_edtinf)->lteSjtCom.sjtkind = ( UCHAR )subject;

很容易以为*优先级很高,而忽略->何尝不是一个运算符!联想Python中的点运算符。

(2021.9.1)

12 i++++i的区别

int i = 0;
int x = i++; //输出为0,即先看到i,直接赋值
i = 0;
int y = ++i; //输出为1,即先看到++,先自增

另外++i可以作为左值,即允许取地址&运算符获得对应的内存地址,而i++步行:

int i = 0;
int *p1 = &(++i);
int *p2 = &(i++);//编译error

++i 和 i++的底层区别

++i,是先取 i 的地址,增加它的内容 ,然后把值放到寄存器中

i++,是先取 i 的地址,把它的值装入寄存器,然后增加内存中 i 的值

关于 ++i 是左值,而 i++ 是右值的问题

++i,返回值是 i 本身自己,是一个变量

i++,返回值是 i 之前的一个数值,是一个数,不是变量

因此 ++(i++) 这就是错误的,因为 i++ 返回的是右值,而不能 ++右值。

关于效率

i++,会产生临时变量,效率比 ++i 要低一些,因此推荐使用 ++i。

(2022.11.30)

补充1 BCD码

BCD:Binary-Coded Decimal,二进码十进数。

十进制有十个数码,理论上,至少用2的4次方16,即4位二进制码来表示。

有许多种类,如8421码,用0000-1001对应0-9。

通常用来表示小数点后的精确值,因为浮点数总是近似的。

(2019.4.2)

补充2 cJson

Json:JavaScript Object Notation.一种轻量的数据交换格式,格式如下:

{				
    "a": 123456,				
    "b": true,				
    "c": "aaabbbccc",				
    "d": [1,2,3],				
    "e": {				
        "g": false,				
        "h": ["1000", "2000"]				
    },				
    "f": null				
}				

cJson:以标准的C写的Json解释器,由cJson.h,cJson.c组成。可以将文本形式的Json解析成链表形式,之后用户可以自定义函数将链表形式转化成结构体形式,反过来也可以将自定义结构体转化为链表形式最后打印成Json文本形式。

Json为文本格式,易于人阅读及机器解析生成,功能与XML类似。

猜你喜欢

转载自blog.csdn.net/watershed1993/article/details/112287562