GUN C compiler extended grammar study notes (3) Inline functions, built-in functions and variable parameter macros

1. Inline functions

1.1 Attribute declaration: noinline

  Two attributes related to inline functions: noinlineand always_inline. The purpose of these two attributes is to tell the compiler, at compile time, whether to inline expand or not to expand the function we specify.
insert image description here
  A function declared using inline is called an inline function, and an inline function is generally preceded by static and extern modifications . Declaring inlinean inline function with a keyword registeris the same as declaring a register variable with a keyword, only suggesting that the compiler expand inline at compile time.
  For function calls, some functions are short and concise, and they are called frequently, and the calling overhead is high, which is not cost-effective. At this time, we can declare this function as an inline function. When the compiler encounters an inline function during compilation, it expands the inline function directly at the call site like a macro, which reduces the overhead of function calls: directly executes the code expanded by the inline function, without saving the scene and Restoration scene.

1.2 Inline functions and macros

  Seeing this, some people may have doubts: inline functions have similar functions to macros, so why not directly define a macro instead of an inline function?
  Inline functions have the following advantages over macros.

  • Parameter type checking: Although inline functions have the expansion characteristics of macros, they are still functions in essence. During the compilation process, the compiler can still perform parameter checking on them, while macros do not have this function.
  • Easy to debug: The debugging functions supported by the function include breakpoints, single step, etc., and inline functions are also supported.
  • Return value: The inline function has a return value, returning a result to the caller. This advantage is relative to ANSI Cthat, because now macros can also have return values ​​and types, such as the macros defined using statement expressions earlier.
  • Interface encapsulation: Some inline functions can be used to encapsulate an interface, but macros do not have this feature.

1.3 Compiler's processing of inline functions

  Inline functions will increase the size of the program. If the inline function is called multiple times in a file and expanded multiple times, the size of the entire program will increase, which will reduce the execution efficiency of the program to a certain extent. The compiler will evaluate according to the actual situation, weigh the pros and cons of expanding and not expanding, and finally decide whether to expand or not. When the compiler expands the inline function, in addition to detecting whether there are pointers, loops, and recursion inside the user-defined inline function, it will also make a trade-off between function execution efficiency and function call overhead.
  Generally speaking, judging whether to expand an inline function, from the programmer's point of view, mainly consider the following factors.

  • The function is small in size.
  • There are no pointer assignment, recursion, loop and other statements in the function body.
  • Frequent calls.
      When we think that a function is small in size and is called frequently and should be expanded inline, we can use static inlinekeywords to modify it. But the compiler does not necessarily do inline expansion. If you want to explicitly tell the compiler that it must be expanded or not, you can use noinlineor always_inlinemake an attribute declaration on the function.

1.4 Why are inline functions defined in header files

   Q: 内联函数为什么要定义在头文件中呢?
   A: 因为它是一个内联函数,可以像宏一样使用,任何想使用这个内联函数的源文件,都不必亲自再去定义一遍,直接包含这个头文件,即可像宏一样使用。
   Q: 为什么还要用static修饰呢?
   A:因为我们使用inline定义的内联函数,编译器不一定会内联展开,那么当一个工程中多个文件都包含这个内联函数的定义时,编译时就有可能报重定义错误。而使用static关键字修饰,则可以将这个函数的作用域限制在各自的文件内,避免重定义错误的发生。

2. Built-in functions

2.1 Definition of built-in functions

  A built-in function is a function implemented inside the compiler. These functions, like keywords , can be called directly , without having to be declared first and then used like standard library functions.
  Function names for built-in functions, usually __builtinbeginning with . These functions are mainly used inside the compiler, mainly for the compiler. The main uses of built-in functions are as follows.

  • Used to handle variable-length argument lists.
  • It is used to handle abnormal program operation, compile optimization, and performance optimization.
  • View the underlying information, stack information, etc. when the function is running.
  • Implements common functions of the C standard library.
      Because the built-in functions are defined inside the compiler and are mainly called by tools and programs related to the compiler, these functions are not documented and change frequently. For application developers, it is not recommended to use these functions. But some functions are very helpful for us to understand the underlying mechanism of program operation and compilation optimization, and these functions are often used in the Linux kernel, so it is very necessary for us to understand some built-in functions commonly used in the Linux kernel.

2.2 Commonly used built-in functions

  There are two main built-in functions commonly used: __builtin_return_address() and __builtin_frame_address().
  __builtin_return_address(), whose function prototype is as follows.
insert image description here
  This function is used to return the return address of the current function or caller. The parameter LEVEL of the function indicates the functions at different levels in the function call chain.

● 0: Get the return address of the current function.
● 1: Obtain the return address of the upper-level function.
● 2: Obtain the return address of the upper-level function.
●...

  Another commonly used built-in function __builtin_frame_address(), its function prototype is as follows.
insert image description here
  In the process of function calling, there is also a concept of stack frame. Every time the function is called, the scene of the current function (return address, registers, temporary variables, etc.) will be saved in the stack, and each layer of function calls will save its own scene information in its own stack . This stack is the stack frame of the current function. Each stack frame has a start address and an end address. A multi-layer function call will have multiple stack frames. Each stack frame will save the start address of the previous stack frame. In this way, each stack frame forms a call chain. We look at the stack frame address of the function
  through the built-in function . ● 0: View the stack frame address of the current function. ● 1: View the stack frame address of the upper-level function. ●...__builtin_frame_address(LEVEL)


2.3 Built-in functions of the C standard library

  Inside the GNU C compiler, the built-in functions of the C standard library implement some built-in functions similar to the C standard library functions. These functions are similar to the functions of the C standard library functions, and the function names are also the same, but a prefix is ​​added in front __builtin.
  Common C standard library functions are as follows.
● Functions related to memory: memcpy()、memset()、memcmp().
● Mathematical functions: log()、cos()、abs()、exp().
● String processing functions: strcat()、strcmp()、strcpy()、strlen().
● Print function: printf()、scanf()、putchar()、puts().
  Using the built-in functions corresponding to the C standard library can also realize the copying and printing of strings, and realize the functions of the C standard library functions.

2.4 Built-in functions:__builtin_constant_p(n)

  This function is mainly used to determine whether the parameter n is a constant at compile time . If it is a constant, the function returns 1, otherwise the function returns 0. This function is often used in macro definitions for compilation optimization. For example, a macro definition may be realized in different ways depending on whether the parameter of the macro is a constant or a variable. For example, the following kernel source code.
insert image description hereinsert image description here

2.5 Built-in functions:__builtin_expect(exp,c)

  Built-in functions __builtin_expect()are also often used for compilation optimization. This function has 2 parameters, and the return value is one of the parameters, still exp. The purpose of this built-in function is to tell the compiler that the value of the parameter exp is very likely to be c, and then the compiler can perform some code optimization on branch prediction based on this hint. The parameter c has nothing to do with the return value of this function. No matter what value c is, the return value of the function is exp. The main use is for compiler branch prediction optimizations .
  Now there are Cache cache devices inside the CPU. The running speed of the CPU is very high, while the speed of the external RAM is relatively low, so when the CPU reads and writes data from the memory RAM, there will be a certain performance bottleneck. In order to improve the efficiency of program execution, the CPU generally caches certain instructions or data through the internal buffer of the CPU, the Cache. When the CPU reads and writes memory data, it will first go to the Cache to see if it can be found: if found, it will directly read and write; If not found, Cache will re-cache part of the data. The CPU reads and writes the Cache much faster than the memory RAM, so this caching method can improve system performance.
  So how does Cache cache memory data ? Simply put, it is based on the principle of spatial proximity . If the CPU is executing an instruction, then in the next clock cycle, the CPU will generally execute the next instruction of the current instruction with a high probability. If the Cache caches the following instructions in the Cache at this time, the CPU can directly fetch, translate and execute instructions from the Cache in the next clock cycle, thereby greatly improving the computing efficiency.
  But sometimes there are surprises. If the program encounters program structures such as function calls, if branches, and goto jumps during execution, it will jump to other places for execution. The instructions originally cached in the Cache are not instructions to be executed by the CPU. At this time, we say that the Cache misses, and the Cache will re-cache the correct instruction code for the CPU to read. This is the basic process of Cache work.
  When we write a program, we always encounter a program structure such as if/switch that selects a branch. It is generally recommended to write the branch with a high probability of occurrence first. **When the program is running, because of the high probability of occurrence, there is no need to jump most of the time. The program is equivalent to a sequential structure, and the cache hit rate of the Cache will also be greatly improved. Some related macros, such as likely and unlikely, have been implemented in the kernel to remind programmers to optimize programs.

2.6 Likely and unlikely in the Linux kernel

  In the Linux kernel, we use the __builtin_expect() built-in function and define two macros. insert image description here
  The main function of these two macros is to tell the compiler: the probability of a certain branch occurring is very high, or very low, and it is basically impossible to occur. Based on this hint information, the compiler will do some branch prediction optimizations when compiling the program.
  There is a detail in the definition of these two macros, which is to perform two negation operations on the parameter x of the macro, which is to convert the parameter x to a Boolean type, and then compare it directly with 1 and 0, telling the compiler that x is The odds are high that it's true or false.

Three, variable parameter macro

  The definition and use of variable parameter functions, the basic routine is to use va_list, va_start, va_end and other macros to parse those variable parameter lists. GNU C doesn't think this is enough, so here's another "god assist": simply macro definitions also support variable parameters!

3.1 Variable parameter macro definition

  The implementation form of a variable parameter macro is actually similar to that of a variable parameter function: use ... to represent a variable parameter list, and the variable parameter list is composed of uncertain parameters, and each parameter is separated by a comma. As shown in the following program. __VA_ARGS__Variable parameter macros use a predefined identifier newly added by the C99 standard to represent the previous variable parameter list, instead of using va_list、va_start、va_endthese macros to parse the variable parameter list like variable parameter functions. When the preprocessor expands the macro, it replaces all __VA_ARGS__identifiers in the macro definition with the variable parameter list.
insert image description here
  The above program will report an error when compiling, resulting in a syntax error. This is because we only passed one parameter to the LOG macro, and the variable parameter is empty. When the macro is expanded, it becomes the following.
insert image description here
  After macro expansion, there is a comma after the first string parameter, which does not conform to the grammar rules, so a grammar error is generated. We need to continue to improve this macro, and use the macro connector ## to avoid this grammatical error.

3.2 Improved version

  We added the macro connector ## in front of the identifier __VA_ARGS__. The advantage of this is: when the variable parameter list is not empty, the function of ## is to connect fmt and the variable parameter list, and the parameters are separated by commas Open, the macro can be used normally; when the variable parameter list is empty, ## also has a special purpose, it will delete the comma after the fixed parameter fmt, so that the macro can be used normally.insert image description hereinsert image description here

3.3 Another way of writing

  When we define a variable parameter macro, in addition to using the predefined identifier __VA_ARGS__ to represent the variable parameter list, the following writing method can also be used.
insert image description here
  The above format is a new way of writing GNU C extension: you can use it directly __VA_ARGS__to args...represent a variable parameter list instead of using it, and then use args directly to represent the variable parameter list in subsequent macro definitions. In order to avoid syntax errors when the variable parameter list is empty, we also need to add a connector between the parameters ##.
insert image description here

3.4 Variadic macros in the kernel

  Variable parameter macros are mainly used for log printing in the kernel. Some driver modules or subsystems sometimes define their own printing macros, which support functions such as printing switch, printing format, and priority control.

insert image description hereinsert image description here
  This macro defines three versions: If we have a dynamic debugging option when compiling the kernel, then this macro is defined as dynamic_pr_debug. If the dynamic debugging option is not configured, we can use the DEBUG macro to control the opening and closing of this macro.
  no_printk() is an inline function, defined in the printk.h header file, and declared through the format attribute, instructing the compiler to check the parameter format according to the printf standard.
  The most interesting thing is the macro dynamic_pr_debug, the macro definition adopts the do{…}while(0) structure. This may seem redundant: our macro will work with it or without it. Anyway, it is executed once, why use this seemingly "superfluous" loop structure? The reason is actually very simple. This definition is to prevent macro ambiguity after the macro is expanded in the statement of the branch structure such as condition and selection.

Guess you like

Origin blog.csdn.net/qq_41866091/article/details/130556039