引入

在C中将一个一个元素的数组放在struct的末尾,可以令每个struct的对象拥有可变数组

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Test{
int xixi ;
char haha[1];
};

int main() {
const char *str = "hhhhhhh";
struct Test *test = (struct Test*)(malloc(sizeof(struct Test) + strlen(str) + 1));
test->xixi = 15;
strcpy(test->haha,str);
return 0;
}

gdb 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
(gdb) p &test
$1 = (struct Test **) 0x29ff28
(gdb) p &test->xixi
$2 = (int *) 0x9a1180
(gdb) x/32xb test
0x9a1180: 0x0f 0x00 0x00 0x00 0x68 0x68 0x68 0x68 # 从 0f 可以看出是大端序
0x9a1188: 0x68 0x68 0x68 0x00 0x0d 0xf0 0xad 0xba # 连续7个 0x68 对应的是 "hhhhhhh"
0x9a1190: 0xab 0xab 0xab 0xab 0xab 0xab 0xab 0xab
0x9a1198: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(gdb) p test->haha[0]
$4 = 104 'h'
(gdb) p test->haha[3]
$5 = 104 'h'

在代码中也可以使用 printf("%c",test->haha[0]) 来访问 ( 我记得应该有边界保护的呀??? )
这个小技巧基本不能在 c++ 中使用,因为 class 中 可以保证相同 access section(public, private,protected) 的数据排列是按照声明顺序排列在内存中,但跨 access section 的数据却不一定是按声明顺序。

1
2
3
4
5
6
...
private:
/** date1 **/
public:
/** date2 **/
...

在内存中 date2 并不一定都排列在 date1 后面。再加上 virtual function ,使得这个方法的有效性更加不确定。(不用就行啦 嘻嘻)

遗留问题

一、c++ 到底干了啥??

strcpy(&test->haha,str); and strcpy(test->haha,str); 在 c99 中都可,但 c++14 只支持后者。同样,c++ 中 strcpy(&(test->haha),str); 也不行

1
2
3
4
5
6
7
8
9
int main() {
const char *str = "hhhhhhh";
struct Test *test = (struct Test*)(malloc(sizeof(struct Test) + strlen(str) + 1));
test->xixi = 15;
strcpy(test->haha,str);
printf("%x\n",test->haha);
fflush(stdout); // 不强制刷新缓冲区的话,debug 看不到 console 输出
return 0;
}
1
2
3
4
printf("%x\n",test->haha); -> 55121c

(gdb) p &(test->haha)
$1 = (char (*)[2]) 0x55121c

可以看到这俩都是 haha 的地址,但后者就是编译不过,所以 c++ 在兼容 c 的时候又干了啥呐?

二、反黑技术哪家强。。

之前有看到,为了防止黑客推理出内存中的数据,所以每次 main帧都不是从栈的最低部开始存的,这个很好验证,每次重新运行,(gdb) p &test 可以看到每次地址都不一样就对了。but … 为什么我 p 出来每次都是一样的??(含泪微笑)
猜测一:领会错了该反黑秘籍
猜测二:需要 release 模式,或者编译优化

三、说好的边界保护呐