可以先看看:
https://my.oschina.net/tsh/blog/974033
http://coolshell.cn/articles/11377.html
这两篇文章。
下面同样通过一个列子来说明:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <stdarg.h>
#include <stdint.h>
struct __attribute__((__packed__)) A
{
int32_t num;
char flags;
char str[];
};
int main(int argc, char *argv[])
{
printf("struct A size = %u\n", sizeof(struct A));
printf("struct A [num addr = %d]\n", &(((struct A *)NULL)->num));
printf("struct A [flags addr = %d]\n", &(((struct A *)NULL)->flags));
printf("struct A [str addr = %d]\n", &(((struct A *)NULL)->str));
return 0;
}
编译输出:
struct A size = 5
struct A [num addr = 0]
struct A [flags addr = 4]
struct A [str addr = 5]
通过使用了 __attribute__((__packed__)) 得到了
struct A 的大小为 5 (int32_t) + (char) = 4 + 1 = 5 发现 char str[] 并没有占用结构体的大小。
接着往下看代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <stdarg.h>
#include <stdint.h>
struct __attribute__((__packed__)) A
{
char flags;
char str[];
};
int main(int argc, char *argv[])
{
struct A *a1 = (struct A *)malloc(sizeof(struct A) + 100);
printf("addr: a1 = %p, a1->flags = %p, a1->str = %p\n",
a1, &(a1->flags), a1->str);
a1->flags = 'A';
a1->str[0] = 'Z';
a1->str[1] = 'Y';
a1->str[2] = 'X';
a1->str[3] = '\0';
printf("%s\n", (char *)a1);
char *str = a1->str;
printf("str = %s\n", str);
printf("str[-1] = %c \n", str[-1]);
printf("str[-1] = %p\n", &str[-1]);
free(a1);
a1 = NULL;
return 0;
}
输出:
addr: a1 = 0x143d010, a1->flags = 0x143d010, a1->str = 0x143d011
AZYX
str = ZYX
str[-1] = A
str[-1] = 0x143d010
这里申请了 101 字节的存储空间
从图中可以看到 str[-1] = flag , 如果已知了 str 那么我们只要 通过 str - sizeof(struct A) 就可以知道 这个结构体的起始地址.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <stdarg.h>
#include <stdint.h>
struct __attribute__((__packed__)) A
{
char flags;
char str[];
};
int main(int argc, char *argv[])
{
struct A *a1 = (struct A *)malloc(sizeof(struct A) + 100);
printf("addr: a1 = %p, a1->flags = %p, a1->str = %p\n",
a1, &(a1->flags), a1->str);
char *str = a1->str;
printf("str[-1] = %p\n", &str[-1]);
struct A *a2 = (struct A *)((str) - sizeof(struct A));
struct A *a3 = (struct A *)((str) - ((size_t)&((struct A *)NULL)->str));
printf("a1 = %p\n", a1);
printf("a2 = %p\n", a2);
printf("a3 = %p\n", a3);
printf("struct A size = %u, str = %u\n", sizeof(struct A), &((struct A *)NULL)->str);
free(a1);
a1 = NULL;
a2 = a3 = NULL;
return 0;
}
编译输出:
addr: a1 = 0x1d21010, a1->flags = 0x1d21010, a1->str = 0x1d21011
str[-1] = 0x1d21010
a1 = 0x1d21010
a2 = 0x1d21010
a3 = 0x1d21010
struct A size = 1, str = 1
在 redis 中大部分的 sds 都会用到 通过 str[-1] 这种方式去获得 flags, 以及 通过 sds - 结构体大小 获得结构体的起始地址操作.
其是在 linux 中 有个宏也可以得到结构体对应成员变量的偏移。
文件: stddef.h offsetof
#ifndef _LINUX_STDDEF_H
#define _LINUX_STDDEF_H
#include <linux/compiler.h>
#undef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
#endif
还是用代码说话 (来自 man -a offsetof):
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
struct s {
int i;
char c;
double d;
char a[];
};
/* Output is compiler dependent */
printf("offsets: i=%ld; c=%ld; d=%ld a=%ld\n",
(long) offsetof(struct s, i),
(long) offsetof(struct s, c),
(long) offsetof(struct s, d),
(long) offsetof(struct s, a));
printf("sizeof(struct s)=%ld\n", (long) sizeof(struct s));
exit(EXIT_SUCCESS);
}
编译输出:
offsets: i=0; c=4; d=8 a=16
sizeof(struct s)=16