嵌入式秋招面试中,你一定要掌握的c基础——关键字详解(1)

哈喽,大家好,这里是自律鸽。正如标题所强调的,在嵌入式秋招面试中,被问到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
#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函数。

完结,撒花。以上是去年我在嵌入式秋招面试中被问到的超高频的知识点,现在全部分享出来,希望对大家有帮忙。文章若有错误,欢迎指出,积极改正~

想看更多精彩内容可关注下面公粽号,谢谢大噶~

#嵌入式##秋招##秋招开了,你想投哪些公司呢#
全部评论
公粽号自律鸽嵌入式
点赞
送花
回复 分享
发布于 06-23 16:48 湖南

相关推荐

初眸:嵌入式不仅看实习也看比赛或者项目,现在还有一个月,可以网上找一个项目尝试然后丰富简历
点赞 评论 收藏
分享
7 40 评论
分享
牛客网
牛客企业服务