一、auto
在C语言中,auto 关键字用于修饰局部变量,尽管在实际编程中,我们往往省略这个关键字,因为局部变量默认就是自动存储类型(auto)的。auto 关键字的主要作用是显式地表明变量的存储类型,并帮助程序员更好地理解代码。
1.1. 作用
-
修饰局部变量,表明该变量是自动存储类型的。
-
虽然通常省略,但在某些情况下,显式使用
auto可以提高代码的可读性。
1.2. 特性
-
自动分配内存 :当函数被调用时,
auto变量会在栈上自动分配内存。 -
自动释放内存 :当函数返回时,
auto变量所占用的内存会自动释放,变量失效。 -
生命周期 :
auto变量的生命周期仅限于定义它的函数或代码块内。
1.3. 代码示例
虽然auto关键字通常被省略,但以下示例可以展示其用法:
arduino
#include <stdio.h>
void myFunction() {
auto int myAutoVar = 10; // 显式使用 auto 关键字,但通常可以省略
printf("Value of myAutoVar: %d\n", myAutoVar);
// myAutoVar 在这里有效,但函数返回后将自动释放内存
}
int main() {
myFunction();
// printf("Value of myAutoVar: %d\n", myAutoVar); // 错误:myAutoVar 在这里无效
return 0;
}

myAutoVar 是一个 auto 类型的局部变量,它在 myFunction 函数内部被定义并初始化。当 myFunction 被调用时,myAutoVar 会在栈上分配内存。当 myFunction 返回时,myAutoVar 所占用的内存会自动释放,变量失效。因此,在 main 函数中尝试访问 myAutoVar 会导致编译错误。
运行结果:

需要注意的是,由于局部变量默认就是
auto类型的,所以在实际编程中,我们通常会省略auto关键字。
二、register
在C语言中,register 关键字用于向编译器提出建议,希望编译器能够将特定的变量存储在CPU的寄存器中,以便提高对该变量的访问速度。寄存器是CPU内部的一种高速存储单元,其访问速度远快于内存。因此,如果某个变量被频繁地读取,且不会被修改,那么将其存储在寄存器中可以显著提高程序的性能。
2.1. 作用
-
register关键字的主要作用是向编译器发出建议,希望编译器能够优化变量的存储位置,将其从内存移动到寄存器中。 -
然而,需要注意的是,这只是一个建议,编译器并不一定会采纳。编译器会根据自身的优化策略、寄存器的可用性以及变量的使用情况来决定是否将变量存储在寄存器中。
2.2. 特性
-
适用于频繁读取且不会被修改的****局部变量 :由于寄存器的数量有限,且读写寄存器需要消耗一定的CPU资源,因此
register关键字最适合用于那些被频繁读取且不会被修改的局部变量。 -
编译器可能会忽略此建议 :如前所述,
register只是一个建议,编译器并不一定会采纳。编译器会根据实际情况来决定是否将变量存储在寄存器中。 -
不能用于全局变量和静态变量:由于全局变量和静态变量在程序的整个生命周期内都有效,且可能会被多个函数访问和修改,因此它们不适合存储在寄存器中。
2.3. 代码示例
下面是一个使用register关键字的简单示例:
arduino
#include <stdio.h>
void count_loops(int n) {
register int i; // 建议编译器将i存储在寄存器中
for (i = 0; i < n; i++) {
// 循环体为空,仅用于演示
}
printf("Loop count: %d\n", i); // 此时i的值应为n
}
int main() {
count_loops(1000000); // 调用函数,传入一个较大的值以演示效果
return 0;
}
使用了register关键字来建议编译器将循环变量i存储在寄存器中。然而,需要注意的是,编译器可能会忽略这个建议,并将i存储在内存中。此外,即使编译器采纳了这个建议,由于寄存器的数量有限,如果程序中使用了大量的register变量,那么编译器也可能无法将它们全部存储在寄存器中。
运行结果:

在使用 **
register**关键字时,我们需要保持谨慎,并意识到它只是一个建议,而不是一个强制性的要求。同时,我们还需要通过实际的性能测试来验证编译器是否采纳了我们的建议,并评估其对程序性能的影响。
三、static
在C语言中,static 关键字有多种用途,包括修饰局部变量、全局变量和函数。
3.1. 修饰局部变量
当static修饰局部变量时,它会延长该变量的生命周期至整个程序运行期间,但变量的作用域仍然保持不变,即只能在定义它的函数或代码块内部访问。意味着,即使函数执行完毕,该变量的值也会保留下来,供下次函数调用时使用。
代码示例:
arduino
#include <stdio.h>
void functionWithStaticVar() {
static int count = 0; // 静态局部变量,只在第一次调用时初始化
count++;
printf("Count: %d\n", count);
}
int main() {
functionWithStaticVar(); // 输出:Count: 1
functionWithStaticVar(); // 输出:Count: 2
functionWithStaticVar(); // 输出:Count: 3
return 0;
}

count是一个静态局部变量。每次调用functionWithStaticVar函数时,count的值都会递增,并且在下次函数调用时保留下来。
实际运行结果:

3.2. 修饰全局变量
当static修饰全局变量时,它会限制该变量的作用域,使其只能在定义它的文件内部访问。有助于避免不同文件之间的命名冲突。
代码示例:
arduino
// file1.c
#include <stdio.h>
static int globalVar = 100; // 静态全局变量,只能在file1.c内部访问
void printGlobalVar() {
printf("GlobalVar in file1.c: %d\n", globalVar);
}
// file2.c
#include <stdio.h>
// 尝试访问file1.c中的globalVar会导致编译错误
// extern int globalVar; // 注释掉这行以避免编译错误
// void printGlobalVarFromFile2() {
// printf("GlobalVar in file2.c: %d\n", globalVar); // 这行会导致链接错误
// }
int main() {
// printGlobalVarFromFile2(); // 注释掉这行以避免编译错误
printGlobalVar(); // 调用file1.c中的函数来打印globalVar
return 0;
}

globalVar是一个静态全局变量,它只能在file1.c内部访问。如果尝试在file2.c中访问它,会导致编译或链接错误。
3.3. 修饰函数
当static修饰函数时,它会使该函数只能在定义它的文件内部使用,防止外部链接。有助于隐藏函数的实现细节,减少命名冲突的可能性。
代码示例:
arduino
// file1.c
#include <stdio.h>
static void internalFunction() {
printf("This is an internal function in file1.c\n");
}
void externalFunction() {
internalFunction(); // 调用内部函数
printf("This is an external function in file1.c\n");
}
// file2.c
#include <stdio.h>
// 尝试调用file1.c中的internalFunction会导致链接错误
// void callInternalFunction() {
// internalFunction(); // 这行会导致链接错误
// }
int main() {
externalFunction(); // 调用file1.c中的外部函数
// callInternalFunction(); // 注释掉这行以避免链接错误
return 0;
}

internalFunction是一个静态函数,它只能在file1.c内部使用。如果尝试在file2.c中调用它,会导致链接错误。而externalFunction是一个外部函数,它可以在其他文件中被调用。
四、extern
在C语言编程中,extern 关键字扮演着至关重要的角色,它允许我们声明在其他文件中定义的变量或函数,从而实现跨文件的资源共享。这是模块化编程的基础,使得我们可以将程序拆分为多个文件,每个文件负责特定的功能或数据结构,然后在需要时通过 extern 声明来访问这些外部定义的资源。
4.1. 作用
extern 关键字的主要作用是声明一个变量或函数是在其他文件中定义的,这样在当前文件中就可以访问到这个变量或函数。它是实现跨文件链接和访问的关键机制。
4.2. 特性
-
跨文件访问 :
extern允许我们访问在其他文件中定义的变量或函数。 -
声明顺序 :在使用
extern声明的变量或函数之前,编译器需要知道它们的存在。因此,extern声明通常放在文件的开头部分,或者在变量或函数被实际使用之前。 -
模块化编程 :
extern是模块化编程的基础,使得我们可以将程序拆分为多个独立的文件,每个文件都可以定义自己的变量和函数,并通过extern声明来访问其他文件中的资源。
4.3. 代码示例
下面是一个简单的示例,展示如何使用 extern 关键字来实现跨文件访问变量和函数。
-
file1.c
#include <stdio.h>
// 定义一个全局变量
int globalVar = 42;
// 定义一个函数
void printGlobalVar() {
printf("GlobalVar in file1.c: %d\n", globalVar);
}
-
file1.h
// 在头文件中使用 extern 来声明 file1.c 中定义的变量和函数
extern int globalVar;
extern void printGlobalVar();
-
file2.c
#include <stdio.h>
#include "file1.h" // 包含 file1.c 的头文件以访问其声明的变量和函数
int main() {
// 访问 file1.c 中定义的全局变量
printf("Accessing globalVar from file2.c: %d\n", globalVar);
kotlin// 调用 file1.c 中定义的函数 printGlobalVar(); return 0;}



编辑
file1.c 定义了一个全局变量 globalVar 和一个函数 printGlobalVar()。然后,在 file1.h 中使用 extern 关键字来声明这些变量和函数,以便在其他文件中访问它们。最后,在 file2.c 中,我们包含了 file1.h 头文件,从而能够访问 file1.c 中定义的变量和函数。
通过这种方式,我们可以将程序拆分为多个文件,每个文件负责特定的功能或数据结构,然后通过 extern 声明和头文件来实现跨文件的资源共享和访问。这是模块化编程的核心思想之一。
五、volatile
volatile 关键字在C/C++等编程语言中用于告诉编译器,某个变量的值可能会在程序的控制流之外被改变。这通常发生在硬件访问、多线程编程或中断服务程序中。使用 volatile 可以防止编译器对该变量进行优化,从而确保每次访问该变量时都能读取其最新的值。
5.1. 作用
-
防止优化 :编译器在优化代码时,可能会将变量的值缓存在寄存器中,以减少对内存的访问。如果变量被声明为
volatile,编译器就不会进行这种优化,而是每次访问该变量时都直接从内存中读取其值。 -
硬件访问 :在嵌入式系统编程中,
volatile常用于访问硬件寄存器的值。这些寄存器的值可能会由硬件本身或其他外部设备改变,因此需要使用volatile来确保每次都能读取到最新的值。 -
多线程编程 :在多线程环境中,一个线程可能会修改另一个线程中的变量。虽然C/C++标准并没有将
volatile定义为线程之间的同步机制,但在某些平台上,使用volatile可以防止编译器对共享变量的优化,从而增加线程间通信的可靠性(尽管这不是跨平台或标准的方法,通常应使用同步原语如互斥锁)。 -
中断服务程序 :在中断服务程序中,全局变量的值可能会由中断处理程序改变。使用
volatile可以确保主程序在访问这些变量时能够读取到最新的值。
5.2. 代码示例
示例1:硬件寄存器访问
arduino
#include <stdint.h>
// 假设有一个硬件寄存器的地址是0x40000000
#define HARDWARE_REGISTER *((volatile uint32_t *)0x40000000)
int main() {
// 读取硬件寄存器的值
uint32_t value = HARDWARE_REGISTER;
// 对寄存器进行写操作
HARDWARE_REGISTER = 0xDEADBEEF;
// 再次读取寄存器的值
value = HARDWARE_REGISTER;
return 0;
}

HARDWARE_REGISTER 是一个宏,它定义了一个指向硬件寄存器地址的 volatile 指针。确保了每次访问 HARDWARE_REGISTER 时都会直接从硬件寄存器中读取或写入值。
示例2:多线程编程中的共享变量
arduino
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 注意:这不是跨平台或标准的方法来实现线程间同步
volatile int shared_variable = 0;
void *thread_function(void *arg) {
// 模拟一些工作
sleep(1);
// 修改共享变量的值
shared_variable = 1;
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);
// 等待线程完成工作
while (shared_variable == 0) {
// 这里可能会进行忙等待,但这不是推荐的做法
// 在实际应用中,应该使用条件变量、信号量等同步原语
}
printf("Shared variable has been changed by the thread.\n");
pthread_join(thread, NULL);
return 0;
}

shared_variable 是一个 volatile 变量,它在多线程环境中被共享。虽然 volatile 在这里可以防止编译器对 shared_variable 的优化,但它并不能保证线程之间的同步。在实际应用中,应该使用互斥锁、条件变量等同步原语来确保线程之间的正确同步。
volatile并不提供原子性保证,即它不能保证对volatile变量的读写操作是原子的。在多线程环境中,即使使用了volatile****,也可能需要额外的同步机制来确保对共享变量的正确访问。
C语言共有32个关键字。这些关键字根据作用可以分为以下四类:
-
数据类型关键字(12个):用于声明变量的数据类型。包括char、double、float、int、long、short、signed、unsigned、struct、union、enum、void等。
-
控制语句关键字(12个):用于控制程序的流程。包括for、do、while、break、continue、if、else、goto、switch、case、default、return等。
-
存储类型关键字(4个或5个):用于声明变量的存储类型。常见的包括auto、extern、static、register等,有时也将typedef视为存储类型关键字的一种,尽管其主要功能是为数据类型取别名。
-
其他关键字(3个或4个):包括const(声明只读变量)、sizeof(计算数据类型长度)、volatile(说明变量在程序执行中可被隐含地改变),有时不包括typedef,因其主要功能是数据类型定义。
这些关键字在C语言编程中具有重要的作用,是构成C语言程序的基本元素之一。掌握这些关键字的使用方法和注意事项,对于学习C语言和进行C语言编程至关重要。
六、小测验
问题:
static关键字的作用?(C语言基础面试高频题,几乎所有公司技术面都会涉及)
答案:
-
修饰局部变量:改变变量的生命周期,使其在程序运行期间一直存在,但作用域不变(只在其定义的函数或代码块内可访问)。函数多次调用时,该变量保持上一次的值。
-
修饰全局变量:限制该全局变量的作用域,使其仅在定义它的源文件内可见,避免与其他源文件中的同名全局变量发生命名冲突。
-
修饰函数:表明该函数是静态函数,只在定义它的源文件内可见,外部文件无法调用。用于隐藏函数实现细节。
问题:
extern和static在修饰全局变量时有何区别?
答案:
-
extern用于声明 一个在其他地方(通常是其他源文件)定义 的全局变量或函数,目的是扩展其作用域,使得当前文件可以访问它。它表示外部链接。 -
static用于修饰全局变量时,是定义 一个静态全局变量,它将该变量的作用域限制 在定义它的本文件内部,外部文件无法通过extern声明来访问它。它表示内部链接。
问题:
volatile关键字的作用?并举例说明其典型应用场景。
答案:
-
作用:告诉编译器,这个变量的值可能会被程序未知的因素(如硬件、中断、其他线程)更改,从而禁止编译器对该变量的读写进行优化(如缓存到寄存器),确保每次使用都直接从内存地址中存取。
-
应用场景:
-
硬件寄存器映射 :如
volatile unsigned int *reg = (volatile unsigned int *)0x12345678;,因为寄存器的值由硬件改变。 -
中断服务程序(ISR)中修改的全局变量:主循环中检查的标志位,可能在ISR中被修改。
-
多线程应用的共享内存区 :一个线程写的变量,另一个线程来读(注意:
volatile不保证原子性,常需与互斥锁等同步机制配合使用)。
-
问题 :C 语言中
static修饰局部变量与普通局部变量的核心区别是什么?(某互联网公司 2024 校招 C 语言岗)
答案:
① 生命周期 :static局部变量为整个程序运行期,普通局部变量仅在函数执行期;
② 存储位置 :static局部变量存全局数据区,普通局部变量存栈区;
③ 初始化 :static局部变量仅第一次调用初始化,普通局部变量每次调用重新初始化。
问题 :
volatile关键字的作用是什么?请列举 2 个典型应用场景。(某嵌入式企业 2023 技术面试)
答案:
作用是告知编译器变量值可能被程序控制流外修改,禁止优化,确保每次直接读取内存最新值;
场景:① 嵌入式硬件寄存器访问;② 多线程环境下的共享变量(需配合同步原语)。
问题 :
extern关键字的作用及使用时的注意事项是什么?
答案:
作用是声明其他文件定义的变量 / 函数,实现跨文件访问;
注意事项:
① 仅声明不定义,需与定义文件的类型一致;
② 通常在头文件中声明,避免重复声明;
③ 需确保定义文件参与编译链接。