offsetof
offsetof
是一个宏,用于计算结构体成员相对于结构体起始位置的偏移量。它通常定义在标准头文件 <stddef.h>
中。
语法如下:
offsetof(type, member)
type
是结构体类型。member
是结构体成员的名字。
这个宏在编译时计算成员在结构体中的偏移量(以字节为单位),并返回一个 size_t
类型的值。
示例解释
假设有一个结构体 ef_txgrp_cb_t
,其中有一个成员 grp_stat
。使用 offsetof
来计算 grp_stat
在 ef_txgrp_cb_t
结构体中的偏移量:
#include <stddef.h>
#include <stdio.h>
typedef struct {
int a;
double b;
char grp_stat;
} ef_txgrp_cb_t;
int main() {
size_t offset = offsetof(ef_txgrp_cb_t, grp_stat);
printf("Offset of grp_stat: %zu\n", offset);
return 0;
}
在这个例子中:
ef_txgrp_cb_t
是一个包含三个成员 (a
,b
,grp_stat
) 的结构体。offsetof(ef_txgrp_cb_t, grp_stat)
计算grp_stat
成员在ef_txgrp_cb_t
结构体中的偏移量。
具体步骤
- 包含
<stddef.h>
头文件:这个头文件定义了offsetof
宏。 - 定义结构体
ef_txgrp_cb_t
:结构体包含多个成员。 - 使用
offsetof
宏:计算grp_stat
成员的偏移量。 - 打印结果:输出偏移量。
计算偏移量
在编译时,编译器会根据结构体的内存布局计算 grp_stat
相对于结构体起始位置的偏移量。这个偏移量取决于前面的成员和它们的对齐要求。
offsetof
宏是 C 标准库的一部分,最早在 ANSI C(也称为 C89 或 C90)标准中引入。具体来说,offsetof
宏定义在 <stddef.h>
头文件中,并在以下 C 语言标准中支持:
- ANSI C (C89/C90): 最早引入
offsetof
宏。 - ISO C90: 与 ANSI C 基本一致。
- ISO C99: 继续支持
offsetof
。 - ISO C11: 继续支持
offsetof
。 - ISO C18: 继续支持
offsetof
。
因此,从 ANSI C 开始,offsetof
宏就一直是 C 标准的一部分,并在后续的所有 C 标准中都得到了支持。这意味着任何符合 ANSI C 或更高版本的 C 编译器都应该支持 offsetof
宏。
printk
原始代码定义:
//函数定义的原始文件:linux-4.19.291\kernel\printk\printk.c
asmlinkage __visible int printk(const char *fmt, ...)
{
va_list args;
int r;
va_start(args, fmt);
r = vprintk_func(fmt, args);
va_end(args);
return r;
}
EXPORT_SYMBOL(printk);
asmlinkage :
表示通过EABI方式,指定函数的调用约定,确保函数参数通过堆栈传递。
__visible:
确保函数或变量在编译器优化过程中不会被隐藏或移除,保持其可见性。
EXPORT_SYMBOL:
将函数或变量导出,使其可以被其他内核模块使用。
va_list:
在 C 语言中,va_list
是一个用于处理可变参数函数的类型。可变参数函数是指那些参数数量不固定的函数,例如 printf
。为了处理这些可变参数,C 标准库提供了一组宏,包括 va_list
、va_start
、va_arg
和 va_end
。
va_list
介绍
va_list
是一个类型,用于存储可变参数的状态。它通常与其他宏一起使用来访问这些参数。
相关宏
va_start
:初始化va_list
变量,使其指向可变参数列表的开始。va_arg
:获取可变参数列表中的下一个参数。va_end
:清理va_list
变量。
使用示例
以下是一个简单的示例,展示如何使用 va_list
和相关宏来实现一个可变参数函数:
#include <stdio.h>
#include <stdarg.h>
// 可变参数函数示例
void my_printf(const char* format, ...) {
va_list args;
va_start(args, format);
// 遍历格式字符串
for (const char* p = format; *p != '\0'; p++) {
if (*p == '%') {
p++;
switch (*p) {
case 'd': {
int i = va_arg(args, int);
printf("%d", i);
break;
}
case 'c': {
int c = va_arg(args, int); // char 被提升为 int
putchar(c);
break;
}
case 's': {
char* s = va_arg(args, char*);
printf("%s", s);
break;
}
default:
putchar('%');
putchar(*p);
break;
}
} else {
putchar(*p);
}
}
va_end(args);
}
int main() {
my_printf("Hello %s! You have %d new messages.\n", "Alice", 5);
return 0;
}
解释
-
va_list args;
:- 声明一个
va_list
类型的变量args
,用于存储可变参数的状态。
- 声明一个
-
va_start(args, format);
:- 初始化
args
变量,使其指向可变参数列表的开始。 format
是最后一个固定参数,va_start
需要知道它的位置,以便找到可变参数的起始位置。
- 初始化
-
va_arg(args, int)
:- 获取可变参数列表中的下一个参数。
- 第一个参数是
va_list
变量,第二个参数是要获取的参数的类型。
-
va_end(args);
:- 清理
va_list
变量。 - 在处理完所有可变参数后调用,以避免资源泄漏。
- 清理
注意事项
- 类型匹配:在使用
va_arg
时,必须确保类型匹配。传递给va_arg
的类型必须与实际参数的类型一致,否则会导致未定义行为。 - 参数提升:某些类型(如
char
和short
)在传递给可变参数函数时会被提升为int
。因此,在使用va_arg
获取这些参数时,应使用int
类型。
总结
va_list
及相关宏提供了一种在 C 语言中处理可变参数的方法。通过这些宏,可以编写灵活的函数,处理不同数量和类型的参数。上述示例展示了如何使用这些宏来实现一个简单的可变参数函数 my_printf
,以处理格式化字符串和不同类型的参数。
使用:
#define pr_emerg(fmt, ...) \
printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
#define pr_alert(fmt, ...) \
printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_crit(fmt, ...) \
printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_err(fmt, ...) \
printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warning(fmt, ...) \
printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warn pr_warning
#define pr_notice(fmt, ...) \
printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
#define pr_info(fmt, ...) \
printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
level:
//linux-4.19.291\include\linux\kern_levels.h
#define KERN_SOH "\001" /* ASCII Start Of Header */
#define KERN_SOH_ASCII '\001'
#define KERN_EMERG KERN_SOH "0" /* system is unusable */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
#define KERN_CRIT KERN_SOH "2" /* critical conditions */
#define KERN_ERR KERN_SOH "3" /* error conditions */
#define KERN_WARNING KERN_SOH "4" /* warning conditions */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
#define KERN_INFO KERN_SOH "6" /* informational */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */
#define KERN_DEFAULT KERN_SOH "d" /* the default kernel loglevel */
...
和 __VA_ARGS__
的作用
...
:在宏定义中,表示宏可以接受任意数量的参数。__VA_ARGS__
:在宏展开时,表示传递给宏的可变参数部分,在 C 语言中,__VA_ARGS__
是一个预处理器技巧,用于处理可变参数宏,##
是一个预处理器运算符,称为“令牌粘合”运算符,用于将两个令牌组合在一起。