环境说明:本文分析基于 x86_64 架构(64位系统),Linux 内核版本 v6.6.143。在此架构下,指针类型占用 8字节。
// trace_event_class 结构体 定义
/* /include/linux/trace_events.h */
struct trace_event_class {
const char *system;
void *probe;
#ifdef CONFIG_PERF_EVENTS
void *perf_probe;
#endif
int (*reg)(struct trace_event_call *event,
enum trace_reg type, void *data);
struct trace_event_fields *fields_array;
struct list_head *(*get_fields)(struct trace_event_call *);
struct list_head fields;
int (*raw_init)(struct trace_event_call *);
};
这是一个trace_event_class结构体的定义,其中
// system 字段
const char *system;
如上图所示,system是一个指针,它指向char类型的数据,即*system,并且const决定了*system变量只读(不可修改)。
疑惑:const决定了*system变量只读的本质是什么?是否意味着const决定了该内存中的数据只读呢?
结构体像一张“蓝图”,而真正存在的是结构体实例。
// event_class_syscall_enter 结构体实例 定义
/* /kernel/trace/trace_syscalls.c */
struct trace_event_class __refdata event_class_syscall_enter = {
.system = "syscalls",
.reg = syscall_enter_register,
.fields_array = syscall_enter_fields_array,
.get_fields = syscall_get_enter_fields,
.raw_init = init_syscall_trace,
};
这是一个trace_event_class结构体的实例event_class_syscall_enter的定义,其中
// system 字段赋值
.system = "syscalls"
.system在语法层面上理解为 访问 system 字段,"syscalls"理解为字符串常量,但研究其本质,为下式
/* 假设 event_class_syscall_enter 的起始地址是 0xffff8000001000 */
*(const char **)(0xffff8000001000 + 0) = "syscalls";
↑
event_class_syscall_enter 的起始地址
从头开始分析这句话:
(const char **):二级指针(0xffff8000001000 + 0):一个地址值(无符号整数)*:解引用我们发现,二级指针但是只解引用了一次,但赋的值是"syscalls",很奇怪。因此我们研究后总结出下式
/* 字符串"syscalls"的首地址,即字符's'的地址是0xffff8000000a5a00 */
*(const char **)(0xffff8000001000 + 0) = 0xffff8000000a5a00
↑
"syscalls" 的首地址

原来真正写入的不是字符串常量,而是字符串常量所在的内存地址,并且这个地址值指向的内存区域是.rodata段,即只读数据段。此时,我们回到第一部分的那个疑惑:const决定了*system变量只读的本质是什么?是否意味着const决定了该内存中的数据只读呢?
我们就会发现,事实上const在此处提供的只读是语法层面的提醒,而非本质原因。真正起决定性作用的是“syscalls”作为字符串常量的身份——它保存在.rodata段,受操作系统物理机制保护,因此天然只读。
我们进行总结一番:字符串常量在程序运行后,已经写入只读数据段了,且通过将字符串常量的地址赋值给system指针使其有意义。
同时,我们发现指针指向的其实是一个char类型数据,而真正有意义的却是字符串数据,这里面是否很矛盾?
实则不然,因为结构体实例的system指针提供一个首地址,真正要读取多长的数据,取决于的不是这个结构体实例本身,而是实现读取数据的函数。
/* /lib/vsprintf.c */
/* Handle string from a well known address. */
static char *string_nocheck(char *buf, char *end, const char *s,
struct printf_spec spec)
{
int len = 0;
int lim = spec.precision;
while (lim--) {
char c = *s++;
if (!c)
break;
if (buf < end)
*buf = c;
++buf;
++len;
}
return widen_string(buf, len, end, spec);
}
if (!c):这里判断c是否为'\0',若是,则停止本文通过研究 Linux v6.6.143 源码,对const char *system进行分析,实现了对 C语言 的指针与内存相关知识进行学习,并且得出了思考结论:指针指向的是一个char类型数据,而真正有意义的是字符串数据(通过其变量名可知其意义)。