巧妙使用offsetof宏定义,在c,c++开发中解决问题

6 篇文章 0 订阅

定义及功能:

#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);

static
void free_data(my_data_t *data) {
	if(data != NULL) {
		free(data);
		data = NULL;
	}
}

void
print_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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值