- 介绍
- 必备知识
- include “filename.h”’和include 有什么区别?
- 静态/非静态、全局/局部 相关知识
- calloc 和 malloc 有什么区别?
- static
- const
- 修饰变量
- 修饰指针
- extren
- volatile
- c语言内存分配方式
- 变量的作用域及生命周期
- 内存对齐(结构体内存大小规则)
- 基础知识
- 实例解析
- 对其原则
- 共用体
- ‘##’连接符
- 宏定义与条件变量
- #if...#else...#endif
- string.h 库函数(以memcpy函数为例)
- void 指针
- 指针大小
- %*c
- strstr()
- atoi()
- rename()
- 面试例题
- 叙述题
- 代码题
- 后续
我们在日常的嵌入式开发中,经常会遇到各种C/C++的使用问题,并且C/C++纯软件的常用开发技巧有些嵌入式并不常用,而嵌入式开发中使用到的C/C++知识与技巧有些也非常特别,这里我们来具体介绍一下嵌入式开发常C/C++知识。
必备知识 include “filename.h”’和include 有什么区别?“filename.h”是从本项目里搜索filename.h, 是从标准库里搜索filename.h文件
静态/非静态、全局/局部 相关知识问题:“静态全局变量”和“非静态全局变量”有什么区别? “静态局部变量”和“非静态局部变量”有什么区别? “静态函数”和“非静态函数”有什么区别? 静态全局变量只在本文件中定义,其他文件不能引用. 局部变量所在函数每次调用的时候都会被重新分配存储空间,函数结束后,就会回收该存储空间。静态局部变量不会,始终保持当前值。
calloc 和 malloc 有什么区别?calloc在动态分配完内存后,将内存空间置为零。malloc不初始化,里边数据是随机的脏数据。
static静态全局变量:在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。静态变量在应用层面上主要是限定作用域。 静态全局变量有以下特点:
- 该变量在全局数据区分配内存
- 未经初始化的静态全局变量会被程序自动初始化为0(在函数体内声明的自动变量的值是随机的,除非它被显式初始化,而在函数体外被声明的自动变量也会被初始化为0)
- 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的 静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况:
一般程序把新产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。 定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:
- 静态全局变量不能被其它文件所用
- 其它文件中可以定义相同名字的变量,不会发生冲突
- static在函数中的用法 当函数中定义一个static变量,除了第一次调用这个函数会定义这个变量以外,其他情况下,均不会重新定义了。下面举个例子,对比静态变量和常规变量在函数调用中的区别。
void staticFun(void)
{
static uint8_t data = 0;
data++;
printf("static function data = %d\r\n",data);
}
void NostaticFun(void)
{
uint8_t data = 0;
data++;
printf("no static function data = %d\r\n",data);
}
int main()
{
staticFun();
staticFun();
staticFun();
NostaticFun();
NostaticFun();
NostaticFun();
return 0;
}
执行此程序,主函数会先调用三次staticFun();函数,再调用三次NostaticFun();函数。最后的输出结果为:
1
2
3
1
1
1
因为每次NostaticFun中的data 都会被重新定义,而staticFun中的data不会重复定义。
const 修饰变量用来修饰不可赋值的变量,如果一个变量在声明初始化之后不希望被修改,可以声明为const; const修饰的变量应该进行初始化; const修饰的变量有可能改变,部分编译器可用scanf修改; const常用来修饰函数的形参,保证该参数在函数内部不会被修改。
修饰指针- const修饰指针——常量指针( const int *p = &a ),指针的指向可以修改,但是指针指向的值不可以修改。
- const修饰常量——指针常量( int * const p = &a ),指针的指向不可以修改,但是指针指向的值可以修改。
- const即修饰指针,又修饰常量(const int * const p = &a ),指针的指向不可以修改,指针指向的值也不可以修改.
extern表明变量或者函数是定义在其他其他文件中的。用来修饰外部变量(全局),表示该变量在其他文件中定义。首先讲一下声明与定义, 声明不等于定义,声明只是指出了变量的名字,并没有为其分配存储空间;定义指出变量名字同时为变量分配存储空间,定义包含了声明。extern是用来声明全局变量的。注意:在程序中一个变量可以声明多次,但只能定义一次。
volatilevolatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)。
c语言内存分配方式- 从静态存储区域分配:由编译器自动分配和释放,在程序编译的时候就已经分配好内存,这块内存在程序的整个运行期间都存在,直到整个程序运行结束时才被释放,如全局变量与static变量。
- 在栈上分配 同样由编译器自动分配和释放,在函数执行时,函数内部的局部变量都可以在栈上创建,函数执行结束时,这些存储单元将被自动释放。 (需要注意的是,栈内存分配运算内置于处理器的指令集中,它的运行效率一般很高,但是分配的内存容量有限。)
- 从堆上分配 也称为动态分配内存,由程序员手动完成申请和释放。程序在运行的时,由程序员使用内存分配函数(如malloc函数)来申请内存,使用完之后再由程序员自己负责使用内存释放函数(如free函数)来释放内存。 (需要注意的是,如果在堆上分配了内存空间,就必须及时释放它,否则将会导致运行的程序出现内存泄漏等错误)
1.全局变量
从静态存储区域分配,其作用域是全局作用域,也就是整个程序的生命周期内都可以使用。如果程序是由多个源文件构成的,那么全局变量只要在一个文件中定义,就可以在其他所有的文件中使用,但必须在其他文件中通过使用extern关键字来声明该全局变量。
2.全局静态变量
从静态存储区域分配,其生命周期也是与整个程序同在的,从程序开始到结束一直起作用。与全局变量不同的是,全局静态变量作用域只在定义它的一个源文件内,其他源文件不能使用。
3.局部变量
从栈上分配,其作用域只是在局部函数内,在定义该变量的函数内,只要出了该函数,该局部变量就不再起作用,也即该变量的生命周期和该函数同在。
4.局部静态变量
从静态存储区域分配,其在第一次初始化后就一直存在直到程序结束。该变量的特点是其作用域只在定义它的函数内可见,出了该函数就不可见了。
内存对齐(结构体内存大小规则) 基础知识在 C/C++ 中,结构体/类是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。编译器为每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。如果在 32 位的机器下,一个int类型的地址为0x00000004,那么它就是自然对齐的。同理,short 类型的地址为0x00000002,那么它就是自然对齐的。char 类型就比较 “随意” 了,因为它本身长度就是 1 个字节。自然对其的前提下:
char 偏移量为sizeof(char) 即 1 的倍数
short 偏移量为sizeof(short) 即 2 的倍数
int 偏移量为sizeof(int) 即 4 的倍数
float 偏移量为sizeof(float) 即 4 的倍数
double 偏移量为sizeof(double) 即 8 的倍数
结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
实例解析- 在设置结构体或类时,不考虑内存对齐问题,会浪费一些空间,例如实验一:
struct asd1{
char a;
int b;
short c;
};//12字节
struct asd2{
char a;
short b;
int c;
};//8字节
上面两个结构体拥有相同的数据成员 char、short 和 int,但由于各个成员按照它们被声明的顺序在内存中顺序存储,所以不同的声明顺序导致了结构体所占空间的不同。具体如下图: 2. 看到上面的第二张图,有的人可能会有疑问,为什么 short 不是紧挨着 char 呢?其实这个原因在上面已经给出了答案——自然对齐。为此,我们可以创建结构体验证自然对齐的规则。实验很简单,在原本 short 类型变量前后添加 char 类型,看结果是怎样的。实验二:
struct asd3{
char a;
char b;
short c;
int d;
};//8字节
struct asd4{
char a;
short b;
char c
int d;
};//12字节
3. 当数据成员中有 double 和 long 时,情况又会有一点变化。还是以上面的结构体 asd1 和 asd2 为基础,都添加 double 型数据成员。来看看结果是什么,实验三:
struct asd1{
char a;
int b;
short c;
double d;
};//24个字节
struct asd2{
char a;
short b;
int c;
double d;
};//16个字节
只添加了一个 double,但 struct asd1 的大小从 12 变到了 24。而 struct asd2 的大小从 8 变到了 16。不需要迷惑,因为这和 double 的自然对其有关(需要注意)。原本的 asd1 占 12 个字节大小,但是 double 对齐需要是 8 的倍数,所以在 short 后面又填充了 4 个字节。此时,asd1 的占 16 个字节,再加上 double 的 8 个字节就成了 24 个字节。而 asd2 没有这个问题,它原本占 8 个字节。因为正好能对齐,所以添加 double 后占 16 个字节。具体情况如下图所示: 4. 指定对齐值 在缺省情况下,C 编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件: 使用伪指令 #pragma pack (n),C 编译器将按照 n 个字节对齐。 使用伪指令 #pragma pack (),取消自定义字节对齐方式。
实验四:
#pragma pack(4)
struct asd5{
char a;
int b;
short c;
float d;
char e;
};//20
#pragma pack()
#pragma pack(1)
struct asd6{
char a;
int b;
short c;
float d;
char e;
};//12
#pragma pack()
使用 #pragma pack (value) 指令将结构体按相应的值进行对齐。两个结构体包含同样的成员,但是却相差 8 个字节。难道我们只需要通过简单的指令就能完成内存对齐的工作吗?其实不是的。上面的对齐结果如下: 以 32 位机器为例,CPU 取的字长是 32 位。所以上面的对齐结果会这样带来的问题是:访问未对齐的内存,处理器需要作两次内存访问。如果我要获取 int 和 float 的数据,处理器需要访问两次内存,一次获取 “前一部分” 的值,一次获取 “后一部分” 的值。这样做虽然减少了空间,但是增加访问时间的消耗。其实最理想的对齐结果应该是:
ps.使用 #pragma pack(4) 可以让前面的实验三中的 asd1 少占用 4 字节。
对齐原则
-
数据类型自身的对齐值:对于 char 型数据,其自身对齐值为1,对于 short 型为2,对于 int,float,double 类型,其自身对齐值为 4,单位字节。
-
结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
-
指定对齐值:#pragma pack (value) 时的指定对齐值 value。
-
数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
根据实际情况,有时需要把几种类型不同的数据,如一个整型变量、一个字符变量、一个实型变量存放在起始地址相同的同一段存储单元种。这三个变量在内存种所占的字节数不同,但都从同一个地址开始存放。这种几个类型不同的变量共同占用同一段内存的结构,称为“共用体”类型结构。共用体,也称为联合体。
union 共用体名
{
成员表列
};
共用体变量的所有成员共享同一段存储空间,这个存储空间等于共用体变量种占据内存字节数最多的成员的字节数。
‘##’连接符##用来连接前后两个参数,把它们变成一个字符串。 例子如下:
#define main(x,y) x##y
int xy=1;
cout
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?