- 基础概述
- 关键字
- volatile
- const
- c语言中的存储类型有auto, extern, register, static
- new、delete、malloc、free关系
- C中struct与union的区别
- a++与++a区别
- sizeof
- 结构体字节对齐
- 判断16位还是32位的系统
- 不使用sizeof,如何求int占用的字节数?
- sizeof和strlen有什么区别?
- define
- 预处理
- C语言宏中"#"和"##"的用法
- ifndef、ifdef、endif
- 预处理器标识#error的目的是什么?
- 定义常量谁更好?# define还是const?
- typedef
- 如何使用define声明个常数,用以表明1年中有多少秒(忽略闰年问题)
- # include< filename. h>和# nclude" filename.h"有什么区别?
- 头文件的作用有哪些?
- 在头文件中定义静态变量是否可行,为什么?
- 有条件引用
- 写一个"标准"宏MIN,这个宏输入两个参数并返回较小的一个?
- 内存
- 内存分配的类型:
- 堆与栈有什么区别?
- 栈在C语言中有什么作用?
- C语言函数参数压栈顺序是怎样的?
- 什么是内存泄漏?
- 简述编译运行一段代码的过程
- 静态链接和动态链接有什么区别
- 陷阱易错
- 类型转换
- 指针和数组
- 指针数组和数组指针
- 数组
- 数组的内存布局
- 数组名 a 作为左值和右值的区别
- &a[0]和&a 的区别
- 指针
- 野指针
- 如何避免野指针?
- C/CPP常用库函数
- strcpy
- strlen
- 移位操作
防止被编译器优化,修饰变量
- 中断里面的变量
- 寄存器类型的变量
- 多次读写的变量
这个关键字一般不使用,在代码后期优化的时候使用
const代码优化阶段
修饰对象成只读,防止误修改,内存只读存储在符号表里面成为常量
1、const 修饰常量
- 比如 const int N = 1;必须赋初值
- const char str[] =“dasa”;修饰字符串
2、const 修饰指针
常量指针
const int* p
其访问*p 不能修改指针的值,只能通过引用的变量修改
int a = 10;
*p = &a;
a = 5 ;
*p = &b;
指针常量
int * const p
其访问的地址不能修改,但是访问地址的值可以修改
*p = 5;
int a = 10;
*p = &a;
a = 5 ;
3、const 函数
参数
防止形参在函数里面被修改
返回值
返回值跟变量声明一致
4、const 数组
常量数组
C语言一切变量或者函数具有两个存储类型与type数据类型的属性
- auto确省条件下,默认申请的变量与函数都是auto存储类型,就是程序使用完这个变量会释放。
- extern,使用其他文件里面的函数或者变量的外部变量。
- register,寄存器变量跟运行环境有关,嵌入式行业硬件不一样,比如32开发板,imx6ull,rk3568,全志v3s这些不同的硬件环境,其寄存器数量不一样。为什么申请存储类型为寄存器,对经常访问的变量方便快速访问。
- static,在函数里面声明,只能初始化一次,下次进入函数不被初始化,在函数体内修改声明变量退出函数也会保留这个内存里面的数值。在函数外作为全局声明表面不能被外界访问。
补充static
- static 修饰变量。在修饰全局变量的时候,作用于限定于被定义的文件中,使用 extern 也不能访问; 在修饰局部变量的时候,在函数体中定义,只能用于该函数,即使使用该函数结束,静态变量也不会被销毁,下次可以继续使用,即缩小了访问范围但是延长了访问时间。
- 修饰函数的时候,函数的作用于仅限于本文件,故也叫内部函数,和修饰全局变量类似。这就是为什么 linux 内核中所有的函数和全局变量都声明static 的原因,linux 内核代码很多,有几万个文件,为了防止文件重名造成冲突,所以就干脆声明为静态的。
- 存储区域:静态存储区(全局储存区)如果是初始化的静态变量则保存在静态存储区的DATA段,如果是未初始化的静态变量则保存在静态存储区的BSS
- 使用场景:我们需要将函数内部定义的变量保存下去或者是作为函数的返回值时,一般菜鸟程序员比如我直接申请全局变量,但是文件多的时候就会造成重入现象的发生。
-
C++中new、delete。new动态申请内存,创建一个对象,执行构造函数对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。delete 会调用对象的析构函数
-
new 对应 free 只会释放内存,new 调用构造函数。malloc与 free 是 C++/C 语言的标准库函数new/delete 是 C++的运算符。
-
mallloc只是做一件事,只是为变量分配了内存,同理,free也只是释放变量的内存。
malloc() 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。如果希望在分配内存的同时进行初始化,请使用 calloc() 函数。【返回值】分配成功返回指向该内存的地址,失败则返回 NULL。 -
new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而 malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针。
- struct是结构体,其是一种复合的数据类型可以定义不同的数据,其内存长度是内部定义的数据类型长度之和,union联合体其长度等于内部定义最大的数据长度,每时刻只能访问一种数据类型,不然程序中最后访问的数据类型会自动覆盖前面访问的数据类型,联合体常用于通信,数据接收不知道为何种变量的时候,把其存放在char数组中,在联合体定义int、float变量,然后直接访问就可以
编程实例union的使用
串口解析接收的两个float变量
float gpslongitude,gpslatitude;
u8 l,k;
union a
{
float sdata;
unsigned char cdata[4];
}b;
union c
{
float cdata;
unsigned char ddata[4];
}d;
union c d;
int i;
void USART3_IRQHandler(void) //串口接收数据且接收的数据包含头尾标识为例。
{//状态位只能有硬件置位也就是置1说明中断产生,软件只能读和清零
if(USART_GetITStatus(USART3,USART_IT_RXNE) != RESET) //中断产生 USART_GetITStatus串口状态检测函数
{
USART_ClearITPendingBit(USART3,USART_IT_RXNE); //清除中断标志
Uart3_Buffer[Uart3_Rx] = USART_ReceiveData(USART3);
// UsartPrintf(USART_DEBUG, "%sn",&Uart3_Buffer[Uart3_Rx]);
Uart3_Rx++;
// UsartPrintf(USART_DEBUG, "Uart3_Buffer");
if(Uart3_Buffer[Uart3_Rx-1]=='B'||Uart3_Rx == Max_BUFF_Len)
{ UsartPrintf(USART_DEBUG, "进入Brn");
if(Uart3_Buffer[0]=='A')
{ UsartPrintf(USART_DEBUG, "进入Arn");
b.cdata[0]=Uart3_Buffer[1];
b.cdata[1]=Uart3_Buffer[2];
b.cdata[2]=Uart3_Buffer[3];
b.cdata[3]=Uart3_Buffer[4];
gpslongitude = b.sdata;//union的特点共用内存
for(i=0;i<50;i++)
{
Uart3_Buffer[i] =0;
}
Uart3_Rx=0;
}
else
{
Uart3_Rx=0;
}
}
if(Uart3_Buffer[Uart3_Rx-1]=='D'||Uart3_Rx == Max_BUFF_Len)
{
UsartPrintf(USART_DEBUG, "进入Drn");
if(Uart3_Buffer[0]=='C')
{ UsartPrintf(USART_DEBUG, "进入Crn");
d.ddata[0]=Uart3_Buffer[1];
d.ddata[1]=Uart3_Buffer[2];
d.ddata[2]=Uart3_Buffer[3];
d.ddata[3]=Uart3_Buffer[4];
gpslatitude = d.cdata;
for(i=0;i<50;i++)
{
Uart3_Buffer[i] =0;
}
Uart3_Rx=0;
}
else
{
Uart3_Rx=0;
}
}
if(Uart3_Buffer[Uart3_Rx-1]=='F'||Uart3_Rx == Max_BUFF_Len)
{
if(Uart3_Buffer[0]=='E')
{
l=Uart3_Buffer[1];
k=Uart3_Buffer[2];
for(i=0;i<50;i++)
{
Uart3_Buffer[i] =0;
}
Uart3_Rx=0;
}
else
{
Uart3_Rx=0;
}
}
}
}
a++与++a区别
- a++会先将自己赋值给一个临时存储变量空间,再进行自加 ++a直接自加效率更高。
- 是C语言的一种单目操作符,如C语言的其他操作符++、–等。它并不是函数与关键字,其返回值是求的变量的数据类型大小。其对结构体求大小也需要考虑字节对齐。
- 结构体内部最大的数据构成大小的最小单位
- 写程序遵循从小到大的定义节约内存
视频讲解
https://blog.csdn.net/qq_40737025/article/details/106738762
宏定义为指针
#includesizeof和strlen有什么区别?using namespace std; #define my_sizeof(L_Value) (char* )(&L_Value + 1) - (char* )&L_Value int main() { int i; cout << my_sizeof(i) << endl; return 0; }
- 一个在编译阶段确定,一个在运行阶段确定
- sizeof计算大小,strlen计算长度
- 定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了。
- 预编译又称为预处理,是做些代码文本的替换工作。处理#开头的指令,比如拷贝#include 包含的文件代码,#define 宏定义的替换,条件编译等,就是为编译做的预备工作的阶段,主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。
- c 编译系统在对程序进行通常的编译之前,先进行预处理。c 提供的预处理功能主要有以下 三种:
1)宏定义 2)文件包含 3)条件编译 - C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤,是预编译的阶段。
C语言执行的阶段,预编译-编译-汇编-链接
其中预处理就是处理这些预处理指令
比如宏定义的替换,条件编译的选择
- #把宏参数变为一个字符串,用##把两个宏参数贴合在一起
参考
- 单片机的时候长期用
- 配合ifdef输出已经定义的错误消息。
- define只是用来进行单纯的文本替换,define常量的生命周期止于编译期,不分配内存空间,它存在于程序的代码段,在实际程序中,它只是一个常数;而const常量存在于程序的数据段,并在堆栈中分配了空间, const常量在程序中确确实实存在,并且可以被调用、传递。
- const常量有数据类型,而define常量没有数据类型。编译器可以对const常量进行类型安全检查,如类型、语句结构等,而define不行。
- typedef 关键字,您可以使用它来为类型取一个新的名字
typedef 与 #define 的区别 - 1、typedef是给数据类型起别名、define不能。
- 2、作用域不同
- 3、对指针的操作不同
#define SECOND_PER_YEAR (60*60*24*365)UL# include< filename. h>和# nclude" filename.h"有什么区别?
- 对于include
,编译器先从标准库路径也就是编译器安装的位置自带的头文件开始搜索flename.h,使得系统文件调用较快。 - 而对于#include"Mflename.h"",编译器先从用户的工作路径也就是自己编写代码的头文件开始搜索filename.h,如果没有找到再去找系统的路径,使得自定义文件较快。
- 1.通过头文件来调用库功能。出于对源代码保密的考虑,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口是怎么实现的。编译器会从库中提取相应的代码。
- 2.头文件能加强类型安全检查。当某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,大大减轻程序员调试、改错的负担。
不可行,如果在头文件中定义静态变量,会造成资源浪费的问题,同时也可能引起程序错误。因为如果在使用了该头文件的每个C语言文件中定义静态变量,按照编译的步骤,在每个头文件中都会单独存在一个静态变量,从而会引起空间浪费或者程序错误所以,不推荐在头文件中定义任何变量,当然也包括静态变量。
有条件引用#if SYSTEM_1 # include "system_1.h" #elif SYSTEM_2 # include "system_2.h" #elif SYSTEM_3 ... #endif写一个"标准"宏MIN,这个宏输入两个参数并返回较小的一个?
#define MIN(A,B) ((A) <= (B) ? (A) : (B))内存 内存分配的类型:
在C/C++中内存分为5个区,分别为栈区、堆区、全局/静态存储区、常量存储区、代码区。
-
①栈区 —— 局部变量 —— 向低地址生长 —— 自动释放 —— 其操作方式类似于数据结构中的栈。
-
②堆区 —— 向高地址生长 —— 手动分配、释放的存储区 —— malloc,free ——它与数据结构中的堆是两回事,分配方式倒是类似于链表
-
③全局/静态存储区static —— 全局变量,静态变量,程序运行结束后自动释放
-
④常量存储区const —— 常量字符串储存在这里。储存在常量区的只读不可写。程序运行结束后自动释放
-
⑤代码区 —— 存放函数体的二进制代码。
-
——> const修饰的全局变量也储存在常量区;
——> const修饰的局部变量依然在栈上
静态内存分配:编译时分配。包括:全局、静态全局、静态局部三种变量。
动态内存分配:运行时分配。包括:栈(stack): 局部变量。堆(heap): c语言中用到的变量被动态的分配在内存中。(malloc或calloc、realloc、free函数)
全部可以划分为五区三段
参考
1.申请方式
stack:栈;由系统自动分配,自动开辟空间
heap:由程序员自己申请并指明大小,c中malloc,c++中new。如p1=(char*)malloc(10);p2=(char*)new(10);但需要注意的是p1,p2本事是在栈中的
2.申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出
堆:首先操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。另外对于大部分系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的delete语句才能正确的释放本内存空间。另外由于找到的堆节点大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
3.申请大小的限制
栈:在windows下栈是向低地址扩展的数据结构,是一块连续的内存区域。所以栈的栈顶地址和最大容量是系统预先设定好的。在windows下栈的大小是2M.因此能从栈获得的空间比较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是是由于系统用链表来存储空闲内存地址的,所以是不连续的。而链表的遍历方向是由低地址到高地址。堆得大小受限于计算机系统中有效的虚拟内存大小。相比较而言堆获得的空间比较灵活,也比较大。
4.申请效率的比较
栈:由系统自动分配,速度较快,但程序员是无法控制的。 堆:由new分配的内存,一般速度比较慢,而且比较容易产生内存碎片,不过用起来最方便。
5.堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数。在大多数c编译器中,参数是由右往左压栈的,然后是函数中的局部变量。静态变量是不入栈的。当函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,,也就是主函数的下一条指令,程序由该点继续执行。
堆:一般是在堆的头部用一个字节存放堆得大小,其他内容自己安排。
6.存取效率的比较
栈在C语言中有什么作用?1 char str1[]=“aaaaaa”; 2 char *str2=“cccccc”;
第一行是在运行时刻赋值的,第二行是在编译时就已经确定的,但在以后的存取过程中,在栈上的数组比指针指向的字符串快。
- 1.C语言中栈用来存储临时变量,临时变量包括函数参数和函数内部定义的临时变量。函数调用中和函数调用相关的函数返回地址,函数中的临时变量,寄存器等均保存在栈中,函数调动返回后从栈中恢复寄存器和临时变量等函数运行场景。
- ⒉.多线程编程的基础是栈,栈是多线程编程的基石,每一个**线程都最少有一个自己专属的栈,用来存储本线程运行时各个函数的临时变量和维系函数调用和函数返回时的函数调用关系和函数运行场景。**操作系统最基本的功能是支持多线程编程,支持中断和异常处理,每个线程都有专属的栈,中断和异常处理也具有专属的栈,栈是操作系统多线程管理的基石。
C语言参数入栈顺序的好处就是可以动态变化参数个数。自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。这样就变成了左边参数的个数不确定,正好和动态参数个数的方向相反。因此,C语言函数参数采用自右向左的入栈顺序,主要原因是为了支持可变长参数形式。
什么是内存泄漏?简单地说就是申请了一块内存空间,使用完毕后没有释放掉。
它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。
源程序是指未经编译的,按照一定的程序设计语言规范书写的,人类可读的文本文件,源程序就是所写好的代码。可执行程序,即常说的.exe程序,可以执行程序,完成计算机功能。在C语言中,.c文件就是所谓的源文件。源程序到可执行程序的过程。在这个过程中,会发生如下的变化:
.c文件生成.obj文件的过程,称为编译,.obj文件生成到.exe文件的过程,称为链接。
.obj文件就是一个是程序编译生成的二进制文件,当.exe文件生成以后.obj文件就会被删除。事实上,.c文件生成.exe文件的过程总共是经历了预处理,编译,汇编,链接,这四个过程。
添加链接描述
静态链接和动态链接有什么区别- 静态链接是指把要调用的函数或者过程直接链接到可执行文件中,成为可执行文件的一部分。换句话说,函数和过程的代码就在程序的exe文件中,该文件包含了运行时所需的全部代码。静态链接的缺点是:当多个程序都调用相同函数时,内存中就会存在这个函数的多个复制,这样就浪费了内存资源
- 动态链接是相对于静态链接而言的。动态链接调用的函数代码并没有被复制到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在操作系统的管理下,才在应用程序与相应的动态链接库Q(dynamic link library,d)之间建立链接关系。当要执行调用,dll文件中的函数时,根据链接产生的重定位信息,操作系统才转去执行,dl文件中相应的函数代码。
- 静态链接的执行程序能够在其他同类操作系统的机器上直接运行。例如,一个exe文件是在Windows2000系统上静态链接的,那么将该文件直接复制到另一台Windows2000的机器上,是可以运行的。而动态链接的执行程序则不可以,除非把该exe文件所需的dll文件都一并复制过去,或者对方机器上也有
1.隐式转换
隐式转换又称为自动转换,这种转换会在计算时自动将表达式中的常量和变量转换成正确的类型,以确保计算结果的正确,而转换的方式则遵循由低类型向高类型的转换。
分析上面的隐式转换,当表达式中出现多种类型时要格外注意,以免出现结果与期望的不符,所以建议在混有多种类型的表达式中采用显式转换。
数组指针就是指向数组的指针,它表示的是一个指针,这个指针指向的是一个数组,它的重点是指针。例如,,int ( *pa)[8]声明了一个指针,该指针指向了一个具有8个int类型元素的数组。下面给出一个数组指针的示例。阿萨
去的是是实时啊啊
int a[5];
sizeof(a)的值为 sizeof(int)*5,32 位系统下为 20。
-
单独访问a,是数组的首地址
-
a[0]是首元素的首地址。
-
然&a[0]和&a的值一样,但其意义不一样。
- a 作为右值时其意义与&a[0]是一样,代表的是数组首元素的首地址
- a 不能作为左值。
虽然&a[0]和&a的值一样,但其意义不一样。
指针 野指针未初始化的指针就是野指针
1.野指针是指向不可用内存的指针,当指针被创建时,没有初始化,指针不可能自动指向NULL,这时默认值是随机的,此时的指针成为野指针。
2.当指针被free或delete释放掉时,如果没有把指针设置为NULL,则会产生野指针,因为释放掉的仅仅是指针指向的内存,并没有把指针本身释放掉。
3.第三个造成野指针的原因是指针操作超越了变量的作用范围。
如何避免野指针?1.对指针进行初始化。
//将指针初始化为NULL。
char * p = NULL;
//用malloc分配内存
char * p = (char * )malloc(sizeof(char));
//用已有合法的可访问的内存地址对指针初始化
char num[ 30] = {0};
char *p = num;
2.指针用完后释放内存,将指针赋NULL。
delete(p); p = NULL;
注: malloc函数分配完内存后需注意:
a.检查是否分配成功(若分配成功,返回内存的首地址;分配不成功,返回NULL。可以通过if语句来判断)
b.清空内存中的数据(malloc分配的空间里可能存在垃圾值,用memset或bzero函数清空内存)
//s是 需要置零的空间的起始地址; n是 要置零的数据字节个数。 void bzero(void *s, int n); // 如果要清空空间的首地址为p,value为值,size为字节数。 void memset(void *start, int value, int size);C/CPP常用库函数
strlen 求数组长度,不包括申请数组默认的/0结束符。
strcpy字符串拷贝函数
注意拷贝的字符串需要小于待拷贝的数组字符串
求字符串长度
移位操作不要直接给寄存器赋目标值是因为你只知道要把目标位设置为某值,但是其他的位你并不知道原本是多少,所以要先读取这个寄存器的整体值,然后再修改其中的目标位,然后再把修改后的值写到寄存器。 连续两位
& 、| 、^ 、~ 、<<、>>
某位置一清0;
Y&=~(1<
Y&=~(3<



