大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
定义及功能:
#include <stddef.h>
#define offsetof(type, member) (size_t)&(((type*)0)->member)
获取类型type中的成员member,相对于type类型的偏移量
将地址0强制转换为type类型的指针,从而定位到member在结构体中偏移位置。
编译器认为0是一个有效的地址,从而认为0是type指针的起始地址。
一个经典的使用场景:
使用offsetof宏,根据已知的一个已经分配空间的结构体对象指针a中的某个成员b的地址,来获取该结构体指针对象a地址。
而结构体a可能是一个比较大的对象,而结构体a的成员b是一个比较小的对象,这个小对象可以在一些数据结构中(比如红黑树中被保存),这样可以根据b反着获取a,从而继续在后续代码中使用a以及a的成员做后续处理。(nginx是如何实现的,见本文最后)
代码简要说明:
1、存在一个较大的结构体a,demo中命名为 my_data_t。实际工程中,这个结构体可以是一个非常大的结构体对象,比如nginx中的ngx_event_t
2、存在一个较小的结构体b,demo中命名为my_str_t。实际工程中,这个结构体可以是一个较小的结构体对象。比如nginx中的ngx_rbtree_node_t
3、为结构体a分配空间,维护结构体a中的成员b的地址。在适当的时候,根据b的地址,以及使用offsetof获取b在a中的偏移量,从而获得a的地址。为后续的程序所用。
上代码:
offsetof_test.h头文件
<pre name="code" class="cpp">/*
* offsetof_test.h
*
* Created on: Jul 9, 2014
* Author: lingyun
*/
#ifndef OFFSETOF_TEST_H_
#define OFFSETOF_TEST_H_
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#define NAME_SIZE 128
typedef struct my_data_s my_data_t;
typedef struct my_str_s my_str_t;
struct my_str_s {
size_t len;
char name[NAME_SIZE];
};
struct my_data_s {
int age;
my_str_t fullname;
char sex;
};
void print_offset();
#endif /* OFFSETOF_TEST_H_ */
offsetof_test.c
</pre><pre name="code" class="cpp">/* * offsetof_test.c * * Created on: Jul 9, 2014 * Author: lingyun */#include "offsetof_test.h"const char *name = "lingyun, liyanhua, lingyutian a family";static void free_data(my_data_t *data);staticvoid free_data(my_data_t *data) { if(data != NULL) { free(data); data = NULL; }}voidprint_offset() { my_data_t *data; size_t size; size_t offset; size_t name_size; name_size = strlen(name); size = sizeof(my_data_t); printf("my_data_t size is : %d\n", size); data = malloc(size); if (data == NULL) { perror("malloc"); goto failed; } size = sizeof(data->age); printf("my_data_t age 's size: %d\n", size); size = sizeof(data->fullname); printf("my_data_t fullname 's size: %d\n", size); size = sizeof(data->sex); printf("my_data_t sex 's size: %d\n", size); offset = offsetof(my_data_t, age); printf("age in my_data_t 's offset is : %d\n", offset); offset = offsetof(my_data_t, fullname); printf("fullname in my_data_t 's offset is : %d\n", offset); offset = offsetof(my_data_t, sex); printf("sex in my_data_t 's offset is : %d\n", offset);failed: free_data(data);}int main() { print_offset(); return 0;}
编译:
gcc -c offsetof_test.c -o offsetof_test.o
gcc -o main offsetof_test.o
./main
运行结果:
函数print_offsetof实现中,主要使用了 offsetof宏定义来获取一个结构体中的各个成员相对于结构体首地址的偏移量
根据结构体定义,不难理解上述输出的结果。
其中age是结构体定义中的第一项,它相对于结构体首地址的偏移地址为0
fullname是结构体的第二项,它相对于结构体首地址的偏移量为 age类型占用的字节数,为4
以后一次类推。
利用已知地址和offsetof来转换一个结构体的首地址
offsetof_test.h
/*
* offsetof_test.h
*
* Created on: Jul 9, 2014
* Author: lingyun
*/
#ifndef OFFSETOF_TEST_H_
#define OFFSETOF_TEST_H_
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#define NAME_SIZE 128
typedef struct my_data_s my_data_t;
typedef struct my_str_s my_str_t;
struct my_str_s {
size_t len;
char name[NAME_SIZE];
};
struct my_data_s {
int age;
my_str_t fullname;
char sex;
};
void use_offsetof();
#endif /* OFFSETOF_TEST_H_ */
offsetof_test.c
/*
* offsetof_test.c
*
* Created on: Jul 9, 2014
* Author: lingyun
*/
#include "offsetof_test.h"
const char *name = "lingyun, liyanhua, lingyutian a family";
static void free_data(my_data_t *data);
static
void free_data(my_data_t *data) {
if(data != NULL) {
free(data);
data = NULL;
}
}
void
use_offsetof() {
my_data_t *data;
size_t size;
size_t offset;
size_t name_size;
my_str_t *fullname_ptr = NULL;
my_data_t *data_ptr = NULL;
name_size = strlen(name);
size = sizeof(my_data_t);
printf("my_data_t size is : %d\n", size);
data = malloc(size);
if (data == NULL) {
perror("malloc");
goto failed;
}
data->age = 32;
memset(data->fullname.name, '\0', NAME_SIZE);
strncpy(data->fullname.name, name, name_size);
data->fullname.len = name_size;
data->sex = 'm';
printf("my_data_t 's instance address: %p\n", data);
printf("my_data_t 's fullname 's address: %p\n", &data->fullname);
printf("char * 's size: %d\n", sizeof(char *));
printf("int * 's size: %d\n", sizeof(int *));
fullname_ptr = &data->fullname;
printf("data->fullname 's address: %p\n", fullname_ptr);
printf("data->fullname 's address(convert to char *): %p\n", (char *)fullname_ptr);
printf("data->fullname 's address(convert to int *): %p\n", (int *)fullname_ptr);
// printf("data->fullname 's address(convert to char * and offset 4): %p\n", ((char *)fullname_ptr) - 4);
//
// printf("data->fullname 's address(convert to int * and offset 1): %p\n", ((int *)fullname_ptr) - 1);
// printf("data->fullname 's address(convert to int * and offset 2): %p\n", ((int *)fullname_ptr) - 2);
// printf("data->fullname 's address(convert to int * and offset 3): %p\n", ((int *)fullname_ptr) - 3);
// printf("data->fullname 's address(convert to int * and offset 4): %p\n", ((int *)fullname_ptr) - 4);
offset = offsetof(my_data_t, fullname);
printf("fullname in my_data_t 's offset is : %d\n", offset);
// data_ptr = (my_data_t *)((int *)fullname_ptr - offsetof(my_data_t, fullname));
data_ptr = (my_data_t *)((char *)fullname_ptr - offsetof(my_data_t, fullname));
printf("data_ptr 's address: %p\n", data_ptr);
printf("name from data_ptr: %s\n", data_ptr->fullname.name);
printf("name length from data_ptr: %d\n", data_ptr->fullname.len);
printf("age from data_ptr: %d\n", data_ptr->age);
printf("sex from data_ptr: %c\n", data_ptr->sex);
failed:
free_data(data);
}
int main() {
use_offsetof();
return 0;
}
编译:
gcc -c offsetof_test.c -o offsetof_test.o
gcc -o main offsetof_test.o
./main
运行结果:
总结:
1、由于知道偏移的字节数为4,所以将fullname_ptr转换为(char *),这样在减去偏移量时,减4逻辑才正确。
如果将fullname_ptr转换为(int *)类型,这样再减4的时候,会在0x8fde00c的基础上,减掉16个字节。一个int占用4个字节,偏移4代表偏移4个int,即16个字节
即如下代码的运行结果是不一样的:
printf("data->fullname 's address(convert to char * and offset 4): %p\n", ((char *)fullname_ptr) - 4);
printf("data->fullname 's address(convert to int * and offset 4): %p\n", ((int *)fullname_ptr) - 4);
2、my_data_t类型中的fullname成员的地址,可以被保存在公用数据结构中,比如hash或tree中作为节点。当遍历hash或tree获取到该节点后,根据上述转换思路,即可获取一个包含fullname的结构体对象指针,为后续处理提供数据。只在hash或tree中保留fullname的地址,是有显而易见的好处的。这样存储空间小,同时在需要fullname的父结构体(my_data_t)类型的指针时,可以很方便的获取。
nginx中是如何使用offsetof来工作的
nginx中采用这一思想,使用 ngx_event_t 和 ngx_rbtree_node_t 完成了定时器到期时,要执行事件时,根据ngx_rbtree_node_t地址来获取ngx_event_t对象指针的实现。
具体参见:
src/event/ngx_event_timer.c 中的如下代码
if ((ngx_msec_int_t) (node->key - ngx_current_msec) <= 0) {
ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
而ngx_event_t类型对象是如何将其结构体中的timer加入到红黑树中的,参见
src/event/ngx_event_timer.h
static ngx_inline void
ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
{
//这里省略了一些代码
ngx_mutex_lock(ngx_event_timer_mutex);
ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);
ngx_mutex_unlock(ngx_event_timer_mutex);
ev->timer_set = 1;
}
注:该代码所属的nginx版本是1.4.2
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/172087.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...