哈喽,大家好,这里是自律鸽。正如标题所强调的,在嵌入式秋招面试中,被问到c语言相关知识的概率几乎达到了百分之百。我本人也在去年的秋招中深切体验了这一点。因此,我想与大家分享一些面试中常问的嵌入式c基础,这些也都是我在求职过程中积累的宝贵经验。
关键字
1、sizeof:经常被问到与strlen()的区别,虽然它们都与计算内存大小有关,但是它们的作用是不同的。两者的区别在于:
● sizeof 是一个运算符,而strlen()是一个函数。
● sizeof计算的是变量或类型所占用的内存字节数,而strlen()计算的是字符串中字符的个数。
● sizeof的语法sizeof(data type),即sizeof可用于计算任何类型的数据;而strlen()的语法strlen(const char* str),即只能用于计算字符串。
● sizeof计算字符串的长度,包含末尾的‘\0’;stlen()计算字符串的长度,不包含字符串末尾的‘\0’。
tips:除了基本数据类型一定要用到sizeof(),其他的都可以不用()。这也是sizeof作为运算符,与函数的不同之处。
老规矩,举例说明:
sizeof(int); //输出4,即整型变量占用4个字节
int x;
sizeof x; //输出4
char s[]="hello,world!";
//tips:啰嗦一句,当数组s没有指定元素数量时,
//编译器会在末尾自动补'\0',因此数组s中实际有13个字符。
sizeof(s);//输出13,即包含'\0'
strlen(s);//输出12,即不包含'\0'
2、static:经常被问到作用。
● 在修饰局部变量时,static修饰的静态局部变量只执行初始化一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。即静态局部变量所在的.c文件运行结束才释放,而普通局部变量在函数调用结束后就释放了。
● 在修饰全局变量时,这个全局变量只能在本文件中访问,不能在其他文件中访问,即便是extern外部声明也不可行。
● 在修饰函数时,这个函数只能在本文件中调用,不能被其他文件调用。
● static修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,均在全局数据区分配内存。初始化的时候自动初始化为0。
tips:extern与static不能同时使用;因为用static修饰的全局变量被限定了作用域,所以其他文件中可以有同名的全局变量被定声明。
为了让大家更好地理解上述知识,老规矩,举例说明:
static修饰局部变量
#include <stdio.h>
void count() {
int counter = 0; // 局部变量
counter++;
printf("%d\r\n", counter);
}
int main() {
count(); // 第一次调用,输出:This is the 1 time this function has been called.
count(); // 第二次调用,输出:This is the 2 time this function has been called.
count(); // 第三次调用,输出:This is the 3 time this function has been called.
return 0;
}
输出结果为:1 1 1
从上述代码可以看出,如果counter没有被声明为static,那么它就是一个普通的局部变量,每次调用count()函数时都会重新初始化。因此,很容易得到输出结果为1 1 1。
#include <stdio.h>
void count() {
static int counter = 0; // 静态局部变量
counter++;
printf("%d\r\n", counter);
}
int main() {
count(); // 第一次调用,输出:This is the 1 time this function has been called.
count(); // 第二次调用,输出:This is the 2 time this function has been called.
count(); // 第三次调用,输出:This is the 3 time this function has been called.
return 0;
}
输出结果为:1 2 3
而在该代码中,通过使用static关键字修饰局部变量counter,确保了counter只在首次进入count()函数时初始化一次。这意味着,在随后的每次count()函数调用中,counter的值都会保持其先前的状态,而不会被重置。因此,每当count()函数被调用时,counter的值都会递增。结果自然是1 2 3。
题外话:分享一个单片机的RAM内存小知识帮助理解。
上图展示了单片机的RAM内存分配结构,自顶部至底部依次为栈区(Stack)、堆区(Heap)、数据段(Data Segment)以及代码段(Code Segment)。从图中可以清晰地看到,局部变量通常存储在栈区(Stack)。这些临时变量在它们的作用域结束时会自动被释放,例如定义在函数内部的局部变量,每次函数调用时都会重新初始化。
相对而言,静态变量则存储在数据段(Data Segment)。这些变量在程序运行期间会一直存在,直到整个程序结束才会被释放,这意味着它们不会在每次函数调用时重新初始化,而是在整个.c文件执行过程中都保持其值。
static修饰全局变量和函数
// file1.c
#include <stdio.h>
static int global_static_var = 42; // 只在 file1.c 中可见
static void helper_function()
{ // 只在 file1.c 中可见
printf("Helper function called.\n");
}
void print_var() {
printf("%d\n", global_static_var);
helper_function(); // 可以在 file1.c 中调用
}
当你在一个源文件中声明了一个全局的static变量时,这个变量仅在该源文件中可见。其他源文件(即使它们包含相同的头文件)也无法访问这个变量。故在另外一个文件file2.c中,你不能直接访问global_static_var。被static关键字修饰的函数亦是如此。
3、const:经常被问到作用,也会问到与#define的区别。
● 作用:用于定义只读的变量,即如果一个变量被const修饰,那么它的值将无法再改变。值得注意的是,const定义的是变量而不是常量。在C99标准中,const定义的变量是全局变量,存放在全局数据区。此外,用const修饰变量时,一定要给变量初始值,否则编译器会报错。
● 与#define的区别:const常量有数据类型,而宏定义常量没有数据类型,只是简单的文本替换。因此,前者具有类型检查,而后者没有;const常量具有作用域,只在定义它的作用域内有效,而宏定义常量没有作用域,可以在文件的任何地方使用;
老规矩,举例说明:
const修饰基本类型变量
#include <stdio.h>
int main() {
const int MAX_VALUE = 100; // 一个整型常量
printf("MAX_VALUE: %d\n", MAX_VALUE);
// MAX_VALUE = 200; // 这行会编译错误,因为MAX_VALUE是常量
return 0;
}
const修饰指针
△ 指向常量的指针(指针可以移动,但指针指向的值不能修改)
#include <stdio.h>
int main() {
int value = 10;
const int *p = &value; // p指向一个整型常量
printf("Value: %d\n", *p);
// *p = 20; // 这行会编译错误,因为*p是常量
return 0;
}
△ 常量指针(指针本身的值不能修改,但指针指向的值可以修改,除非指针指向的也是一个常量)
#include <stdio.h>
int main() {
int value = 10;
int *const q = &value; // q是一个指向整型的常量指针
printf("Value: %d\n", *q);
// q = &another_value; // 这行会编译错误,因为q是常量指针
return 0;
}
△ 指向常量的常量指针(指针本身的值不能修改,指针指向的值也不能修改)
#include <stdio.h>
int main() {
const int value = 10;
const int *const r = &value; // r是一个指向整型常量的常量指针
printf("Value: %d\n", *r);
// *r = 20; // 这行会编译错误,因为*r是常量
// r = &another_value; // 这行也会编译错误,因为r是常量指针
return 0;
}
const修饰数组
#include <stdio.h>
int main() {
const int array[] = {1, 2, 3, 4, 5}; // 一个整型常量数组
for (int i = 0; i < 5; i++) {
printf("%d ", array[i]);
}
// array[0] = 10; // 这行会编译错误,因为array是一个常量数组
return 0;
}
const修饰结构体成员
#include <stdio.h>
typedef struct {
const int x; // 结构体的一个整型常量成员
int y;
} Point;
int main() {
Point p = {10, 20};
printf("Point: (%d, %d)\n", p.x, p.y);
// p.x = 30; // 这行会编译错误,因为p.x是常量
p.y = 40; // 这是允许的,因为p.y不是常量
return 0;
}
4、volatile:经常被问到作用和理解。
首先在这讲解这个关键字之前,给大家介绍一下编译器的一个优化操作。由于内存访问的速度远不及cpu处理速度,为了提高存取速度,编译器优化时会把内存变量缓存到寄存器中,若变量由其他程序所改变(核心要义:内存中的值被改变、而寄存器中的值未被改变),将会出现不一样的现象。
作用如下:
● 告诉编译器不要缓存变量:当你将一个变量声明为volatile时,编译器会知道这个变量可能在任何时候被外部因素(如硬件、中断服务程序、线程等)该变,因此它不会对这个变量的访问进行优化。
● 确保每次访问都是直接从内存中读取:每次读取volatile变量时,编译器都会生成代码来从内存中读取该变量的值,而不是使用之前存储在寄存器或者其他地方的值。
● 在多线程和硬件交互中特别有用:在多线程环境中,一个线程可能正在修改一个变量,而灵一个线程正在读取该变量。如果不使用volatile,编译器可能会优化读取操作,导致读取到的是旧值。同样,当与硬件交互时,硬件可能会在任何时候该变某个内存位置的值,因此也需要使用volatile来确保读取到的是最新的值。
生硬的知识点还是得用例子来说明:
volatile int flag = 0; // 声明一个volatile整数变量
// 在某个中断服务程序中
void ISR() {
flag = 1; // 改变flag的值
}
// 在主循环中
while(1) {
if(flag) { // 检查flag的值
// 处理flag被设置的情况
flag = 0; // 重置flag
}
// ... 其他代码 ...
}
在上面的示例中,flag是一个volatile变量,它在中断服务程序中被改变,并在主循环中被检查。由于flag是volatile的,编译器不会对其访问进行优化,从而确保每次读取的都是最新的值。
5、extern:经常被问到作用。
● 当在多个源文件中共享全局变量时,通常在一个源文件中定义该变量(即分配存储空间),而在其他源文件中使用extern声明该变量,以便能够访问它。
举个例子:
示例1:假如我们有两个文件:global.c和main.c
global.c
#include <stdio.h>
// 定义全局变量
int globalVar = 100;
void printGlobalVar() {
printf("Value of globalVar in global.c: %d\n", globalVar);
}
main.c
#include <stdio.h>
// 声明全局变量(在其他文件中定义)
extern int globalVar;
extern void printGlobalVar(); // 声明在其他文件中定义的函数
int main() {
printf("Value of globalVar in main.c: %d\n", globalVar);
printGlobalVar();
return 0;
}
在这个例子中,若你想共享全局变量,在文件中用extern声明即可。
示例2:假设我们有一个头文件functions.h和两个源文件functions.c和main.c。
functions.h
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
// 声明函数
extern void printMessage();
#endif // FUNCTIONS_H
functions.c
#include <stdio.h>
#include "functions.h"
// 定义函数
void printMessage() {
printf("Hello, World!\n");
}
main.c
#include <stdio.h>
#include "functions.h"
int main() {
printMessage(); // 调用在functions.c中定义的函数
return 0;
}
在这个例子中,printMessage函数在functions.c中定义,并在functions.c中通过extern声明。然后,在main.c中包含functions.h,从而可以访问printMessage函数。
完结,撒花。以上是去年我在嵌入式秋招面试中被问到的超高频的知识点,现在全部分享出来,希望对大家有帮忙。文章若有错误,欢迎指出,积极改正~
想看更多精彩内容可关注下面公粽号,谢谢大噶~
标签:const,变量,int,嵌入式,详解,static,秋招,main,常量 From: https://blog.csdn.net/ContikiNewer/article/details/139886241