【嵌入式面经专题】1-C语言基础

参考文章1:嵌入式软件面经杂谈

本源文章:《嵌入式面经》

1. 嵌入式关键字volatile有什么含意 并给出三个不同的例子

参考文章1:嵌入式面经

参考文章2:关于STM32库中 __IO 修饰符(volatile修饰符)

volatile 初印象

最初接触到 volatile,是看野火的自己编写库函数的章节,其中在寄存器结构体中的寄存器成员前加了 "__IO" 修饰,这个 __IO 就是 volatile。

1 //volatile 表示易变的变量,防止编译器优化
2 #define __IO volatile
3 typedef unsigned int uint32_t;
4 typedef unsigned short uint16_t;
5
6 /* GPIO 寄存器列表 */
7 typedef struct {
8 __IO uint32_t MODER; /*GPIO 模式寄存器 地址偏移: 0x00 */
9 __IO uint32_t OTYPER; /*GPIO 输出类型寄存器 地址偏移: 0x04 */
10 __IO uint32_t OSPEEDR; /*GPIO 输出速度寄存器 地址偏移: 0x08 */
11 __IO uint32_t PUPDR; /*GPIO 上拉/下拉寄存器 地址偏移: 0x0C */
12 __IO uint32_t IDR; /*GPIO 输入数据寄存器 地址偏移: 0x10 */
13 __IO uint32_t ODR; /*GPIO 输出数据寄存器 地址偏移: 0x14 */
14 __IO uint16_t BSRRL; /*GPIO 置位/复位寄存器低 16 位部分 地址偏移: 0x18 */
15 __IO uint16_t BSRRH; /*GPIO 置位/复位寄存器 高 16 位部分地址偏移: 0x1A /
16 __IO uint32_t LCKR; /GPIO 配置锁定寄存器 地址偏移: 0x1C /
17 __IO uint32_t AFR[2]; /GPIO 复用功能配置寄存器 地址偏移: 0x20-0x24 /
18 } GPIO_TypeDef;

当时火哥大致讲解了原因:

寄存器很多时候是由外设或 STM32 芯片状态修改的,也就是说即使 CPU 不执行代码修改这些变量,变量的值也有可能被外设修改、更新,所以每次使用这些变量的时候,我们都要求 CPU 去该变量的地

址重新访问。

若没有这个关键字修饰,在某些情况下,编译器认为没有代码修改该变量,就直接从 CPU 的某个缓存获取该变量值,这时可以加快执行速度,但该缓存中的是陈旧数据,与我们要求的寄存器最新状态可能会有出入。

如果不用volatile,编译器会有优化操作:在同一进程中当上一次对这个地址操作的值在该进程中没有被修改时候,它会自动把上次读的数据取出来,而不是重新从这个地址取内容。在嵌入式开发中对寄存器或I/O端口的操作都要用volatile。

总结一下:

  1. 当变量可变允许除了程序之外的比如硬件来修改他的内容时,应该用 volatile 修饰。
  2. 加volative的原理是:访问该数据任何时候都会直接访问该地址处内容,即通过cache提高访问速度的优化被取消。

 

应用场合:

场合1:多线程变量

对于共享的内存地址(多线程变量),多个程序都对它操作的时候。你的程序并不知道,这个内存何时被改变了。如果不加这个voliatile修饰,程序是利用catch当中的数据,那个可能是过时的了,加了 voliatile,就在需要用的时候,程序重新去那个地址去提取,保证是最新的。

场合2:外设通过映象的方式的内存

现在硬件设备往往也有自己的私有内存地址,比如显存,他们一般是通过映象的方式,反映到一段特定的内存地址当中、因此当其自己改变了其内存的数据时,程序的缓存的数据可能还没有改。

场合3:硬件寄存器(尤其指:状态寄存器

 

扩展:register关键字

1.介绍
在嵌入式系统中,寄存器是位于CPU内部的高速存储器,用于存储临时数据和执行指令。使用寄存器变量可以提高程序的执行速度和效率,因为寄存器的访问速度比内存快得多。
当使用register关键字声明变量时,编译器会尽可能地将该变量存储在寄存器中,以便快速访问。然而,嵌入式系统的编译器可能会忽略register关键字,因为寄存器的数量有限,编译器需要根据需要进行优化和分配寄存器。

2.应用
关键性能代码:对于一些关键性能代码,例如内部循环或频繁执行的代码段,可以使用register关键字声明相关的变量。这样可以减少对内存的访问延迟,从而提高代码的执行速度。
中断处理程序:在嵌入式系统中,中断处理程序的执行时间通常要求非常短。通过使用register关键字声明一些关键变量,可以减少对内存的访问,提高中断处理程序的响应速度。

硬件接口:与外部硬件设备进行通信时,通常需要频繁读写寄存器。通过使用register关键字声明与硬件接口相关的变量,可以加快对寄存器的访问速度,提高数据传输的效率

#include <stdio.h>
 
int main()
{
register int a=5,i;
for(i=0;i<5;i++)
{
    a--;
}
return 0;
}

 

2. 关键字static的作用是什么?局部变量一定存在栈中吗?

我的回答:

①static 在全局变量、函数前,防止其他文件引用。(作用域的概念)

②static 在局部变量前,可以起到调用后只初始化一次的效果。

 

局部变量不一定在栈中,用 static 修饰就不会。

上面的②没有触达本质,之所以能起到这种效果,是因为 static 修饰的局部变量存储在静态存储区(函数之外不会销毁的区域)。

该问题的重点是要清楚作用域静态存储区的概念,以及两者的区别。全局变量一般都在静态存储区,局部变量常常不在静态存储区,但也有可能在。

 

补充回答:

全局静态变量
在全局变量前加上关键字static,全局变量就定义成一个全局静态变量.
内存中的位置:静态存储区,在整个程序运行期间一直存在。
初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);
作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。

局部静态变量
在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。
内存中的位置:静态存储区
初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);
作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;

 

3. C语言运算符优先级

问: 已知a为某一结构体,b为其中一个成员,想要得到其地址,试问:这样的表达式:  &(a.b)  中的括号是否有必要。

答:没有必要。 . 的优先级比 & 要高。

C语言运算符优先级如下所示:

在C语言中,运算符优先级是一个比较麻烦的概念,如果搞不清楚优先级可能会产生一些难以察觉的错误

第一优先级:[ ] ( ) . ->
第一优先级包括方括号,圆括号,对象,对象指针

第二优先级:- ~ ++ – * & ! ( 类型 ) sizeof
第二优先级包括取负,按位取反,自增,自减,取值运算符,取地址符,逻辑非运算符,强制类型转换,长度运算符

第三优先级: / * %
第三优先级包括乘法,除法,取模

第四优先级:+ -

表示加法,减法
注意第四优先级中的 - 表示减法,而非第一优先级中的取负

第五优先级:<< >>
分别表示左移,右移

第六优先级: > >= < <=
大于(等于) 小于(等于)

第七优先级: == !=
逻辑表达中的等于,不等于

第八优先级:&
表示按位与,和取地址符不是一个概念,一般用法为 表达式&表达式

第九优先级:^
表示按位异或,用法为 表达式^表达式

第十优先级: |
按位或 用法为 表达式 | 表达式

第十一优先级:&&
表示逻辑与

第十二优先级:||
表示逻辑或

第十三优先级:?:
表示条件运算符,用法为 表达式1?表达式2:表达式3
如果表达式1成立,则结果为表达式2;不成立,则为表达式3

第十四优先级:= /= *= %= += -= <<= >>= &= ^= |=
分别表示赋值运算,除后赋值,乘后赋值,取模后赋值,加后赋值,减后赋值,左移后赋值,右移后赋值,按位与后赋值,按位异或后赋值,按位或后赋值

第十五优先级: ,
表示逗号运算符,用于表达式之间,从左到右,其值为最后一个表达式的值
————————————————
版权声明:本文为CSDN博主「爱一袭铁路」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_51054450/article/details/112748644

 

4- sizeof() 的用法以及与 strlen() 的区别

试问:以下程序打印出来多少?

 答案是:40 

sizeof是C/C++中的一个操作符(operator),简单的说其作用就是返回一个对象或者类型所占的内存字节数。

扩展:

一. 基本类型的sizeof()

        对于基本类型,sizeof都将等于其基本类型的长度

                sizeof(int);           //值为4
                sizeof(double);    //值为8

                sizeof(10);          //值为4
                sizeof(8.8);        //值为8

二. 指针类型的sizeof()

        对于指针类型,sizeof都将等于计算机内部地址总线的长度,32位机器将等于4

              char *p;
              sizeof(p);  //值为4
View Code

试问:sizeof 与 strlen 的区别:

  • sizeof是一个操作符,而strlen是库函数。
  • sizeof的参数可以是数据的类型,也可以是变量,而strlen只能以结尾为’\0’的字符串作参数。
  • 编译器在编译时就计算出了sizeof的结果,而strlen必须在运行时才能计算出来。
  • sizeof计算数据类型占内存的大小(字符串成员有'\0'时,包含'\0'),strlen计算字符串实际长度(不包含 '\0)

 

posted @ 2023-06-19 21:59  FBshark  阅读(43)  评论(0编辑  收藏  举报