有用的C语言相关函数

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 是一个包含三个成员 (abgrp_stat) 的结构体。
  • offsetof(ef_txgrp_cb_t, grp_stat) 计算 grp_stat 成员在 ef_txgrp_cb_t 结构体中的偏移量。

具体步骤

  1. 包含 <stddef.h> 头文件:这个头文件定义了 offsetof 宏。
  2. 定义结构体 ef_txgrp_cb_t:结构体包含多个成员。
  3. 使用 offsetof 宏:计算 grp_stat 成员的偏移量。
  4. 打印结果:输出偏移量。

计算偏移量

在编译时,编译器会根据结构体的内存布局计算 grp_stat 相对于结构体起始位置的偏移量。这个偏移量取决于前面的成员和它们的对齐要求。

offsetof 宏是 C 标准库的一部分,最早在 ANSI C(也称为 C89 或 C90)标准中引入。具体来说,offsetof 宏定义在 <stddef.h> 头文件中,并在以下 C 语言标准中支持:

  1. ANSI C (C89/C90): 最早引入 offsetof 宏。
  2. ISO C90: 与 ANSI C 基本一致。
  3. ISO C99: 继续支持 offsetof
  4. ISO C11: 继续支持 offsetof
  5. 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_listva_startva_arg 和 va_end

va_list 介绍

va_list 是一个类型,用于存储可变参数的状态。它通常与其他宏一起使用来访问这些参数。

相关宏

  1. va_start:初始化 va_list 变量,使其指向可变参数列表的开始。
  2. va_arg:获取可变参数列表中的下一个参数。
  3. 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;
}

解释

  1. va_list args;

    • 声明一个 va_list 类型的变量 args,用于存储可变参数的状态。
  2. va_start(args, format);

    • 初始化 args 变量,使其指向可变参数列表的开始。
    • format 是最后一个固定参数,va_start 需要知道它的位置,以便找到可变参数的起始位置。
  3. va_arg(args, int)

    • 获取可变参数列表中的下一个参数。
    • 第一个参数是 va_list 变量,第二个参数是要获取的参数的类型。
  4. 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__ 是一个预处理器技巧,用于处理可变参数宏, ## 是一个预处理器运算符,称为“令牌粘合”运算符,用于将两个令牌组合在一起。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值