笔者在阅读一些开源代码时,发现很多头文件都会使用static inline关键字,以linux内核的list.h函数为例:
/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
这不禁让笔者产生了好奇,static笔者一般是用来修饰变量或函数限定作用域实现封装功能,inline一般是为了效率考虑,建议编译器选择展开函数,而不是直接ldr调用,那么static inline是什么用法呢?
先放结论:static是为了防止重定义错误,因为该函数定义在头文件,编译时会被替换到引入的源文件中,不使用static关键字,那么会出现多个函数重定义问题。至于inline,就是为了展开该函数。
现在我们知道这与编译有关,但是在讲解编译的基本知识前,读者不妨做个小实验验证一下结论:
headfun.h
//
// Created by el on 2024/9/8.
//
#ifndef TRY_CONFIG_H
#define TRY_CONFIG_H
inline int config1()
{
int a = 1;
return a;
}
#endif //TRY_CONFIG_H
one.c
//
// Created by el on 2024/9/8.
//
#include "one.h"
#include "config.h"
int firsttry()
{
int a =config1();
return a;
}
one.h
//
// Created by el on 2024/9/8.
//
#ifndef TRY_ONE_H
#define TRY_ONE_H
int firsttry();
#endif //TRY_ONE_H
two.c
//
// Created by el on 2024/9/8.
//
#include "two.h"
#include "config.h"
int twotry()
{
int a =config1();
return a;
}
two.h
//
// Created by el on 2024/9/8.
//
#ifndef TRY_TWO_H
#define TRY_TWO_H
int twotry();
#endif //TRY_TWO_H
程序运行,就会报重定义错误,对headfun.h文件进行修改:
headfun.h
//
// Created by el on 2024/9/8.
//
#ifndef TRY_CONFIG_H
#define TRY_CONFIG_H
static inline int config1()
{
int a = 1;
return a;
}
#endif //TRY_CONFIG_H
此时报错就会消失。
c程序在被编译时,会先进行预处理,把源文件中的头文件声明换成对应头文件内容。这就是static存在的意义-限制函数作用域,防止重定义。
预处理器详细流程如下:
C/C++预处理器(Preprocessor)是C/C++编译过程的第一步。它主要负责处理源代码文件中的预处理指令。预处理指令通常以#
开始,例如#include
、#define
、#if
等。预处理器的工作流程可以分为以下几个主要步骤:
- 宏定义替换:预处理器会查找所有的宏定义,然后替换程序中的宏。
- 头文件处理:对于
#include
指令,预处理器会将指定的文件内容插入到该指令所在的位置。 - 条件编译:预处理器会根据
#if
、#ifdef
、#ifndef
、#else
、#elif
和#endif
指令,决定是否编译代码的特定部分。 - 移除注释:预处理器移除源代码中的所有注释。
- 添加行号和文件名信息:为了在编译过程中产生准确的错误和警告信息,预处理器会添加行号和文件名信息。
- 生成预处理后的源代码:完成上述步骤后,预处理器会生成一个预处理后的源代码文件,这个文件将被编译器用于后续的编译过程。
至于static发挥作用,如果要详细的说,是在生成符号表时,编译器会为每一个作用域建立单独的符号来实现作用域。每一个带有声明的程序块都有自己的符号表。