176. C/C++ 结构体的成员数组和指针

现象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

struct str{
int len;
char s[0];
};

struct foo {
struct str *a;
};

int main(int argc, char** argv) {
struct foo f={0};
if (f.a->s) {
printf( f.a->s);
}
return 0;
}

使用gdb调试如下:

你编译一下上面的代码,在VC++和GCC下都会在14行的printf处crash掉你的程序。

接下来,你调试一下,或是你把14行的printf语句改成:

1
printf("%x\n", f.a->s);

你会看到程序不crash了。程序输出:4。 这下你知道了,访问0x4的内存地址,不crash才怪。于是,你一定会有如下的问题:

  1. 为什么不是 13行if语句出错?f.a被初始化为空了嘛,用空指针访问成员变量为什么不crash?
  2. 为什么会访问到了0x4的地址?靠,4是怎么出来的?
  3. 代码中的第4行,char s[0] 是个什么东西?零长度的数组?为什么要这样玩?

结构体中的成员

首先,我们需要知道——所谓变量名,其实是内存地址的一个抽像名字罢了, 而变量就是内存空间。在静态编译的程序中,所有的变量名都会在编译时被转成内存地址。机器是不知道我们取的名字的,只知道地址。

所以有了==栈内存区,堆内存区,静态内存区,常量内存区==,我们代码中的所有变量都会被编译器预先放到这些内存区中。

有了上面这个基础,我们来看一下结构体中的成员的地址是什么?我们先简单化一下代码:

1
2
3
4
struct test{
int i;
char *p;
};

上面代码中,test结构中i和p指针,在C的编译器中保存的是==相对地址==. 也就是说,他们的地址是相对于struct test的实例的。如果我们有这样的代码:

1
struct test t;

我们用gdb跟进去,对于实例t,我们可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# t实例中的p就是一个野指针
(gdb) p t
$1 = {i = 0, c = 0 '\000', d = 0 '\000', p = 0x4003e0 "1\355I\211\..."}

# 输出t的地址
(gdb) p &t
$2 = (struct test *) 0x7fffffffe5f0

#输出(t.i)的地址
(gdb) p &(t.i)
$3 = (char **) 0x7fffffffe5f0

#输出(t.p)的地址
(gdb) p &(t.p)
$4 = (char **) 0x7fffffffe5f4

我们可以看到,t.i的地址和t的地址是一样的,t.p的址址相对于t的地址多了个4。说白了,**t.i 其实就是(&t + 0x0)**, **t.p 的其实就是 (&t + 0x4)**。0x00x4这个偏移地址就是成员i和p在编译时就被编译器给==hard code==了的地址。于是,你就知道,==不管结构体的实例是什么——访问其成员其实就是加成员的偏移量==。

下面我们来做个实验:

1
2
3
4
5
6
7
8
9
10
struct test{
int i;
short c;
char *p;
};

int main(){
struct test *pt=NULL;
return 0;
}

编译后,我们用gdb调试一下,当初始化pt后,我们看看如下的调试:(我们可以看到就算是pt为NULL,访问其中的成员时,其实就是在访问相对于pt的内址)

1
2
3
4
5
6
7
8
(gdb) p pt
$1 = (struct test *) 0x0
(gdb) p pt->i
Cannot access memory at address 0x0
(gdb) p pt->c
Cannot access memory at address 0x4
(gdb) p pt->p
Cannot access memory at address 0x8

注意:上面的pt->p的偏移之所以是0x8而不是0x6,是因为内存对齐了(我在64位系统上)。关于内存对齐,可参看《深入理解C语言》一文。

好了,现在你知道为什么原题中会访问到了0x4的地址了吧,因为是相对地址。

相对地址有很好多处,其可以玩出一些有意思的编程技巧,比如把C搞出面向对象式的感觉来,你可以参看我正好11年前的文章《用C写面向对像的程序》(用指针类型强转的危险玩法——相对于C++来说,C++编译器帮你管了继承和虚函数表,语义也清楚了很多)

指针和数组的差别

有了上面的基础后,你把源代码中的struct str结构体中的char s[0];改成char *s;试试看,你会发现,在13行if条件的时候,程序因为Cannot access memory就直接挂掉了。为什么声明成char s[0],程序会在14行挂掉,而声明成char *s,程序会在13行挂掉呢?那么char *s 和 char s[0]有什么差别呢

在说明这个事之前,有必要看一下汇编代码,用GDB查看后发现:

  • 对于char s[0]来说,汇编代码用了lea指令,lea 0x04(%rax), %rdx
  • 对于char*s来说,汇编代码用了mov指令,mov 0x04(%rax), %rdx

lea全称load effective address,是把地址放进去,而mov则是把地址里的内容放进去。所以,就crash了。

从这里,我们可以看到,访问成员数组名其实得到的是数组的相对地址,而访问成员指针其实是相对地址里的内容(这和访问其它非指针或数组的变量是一样的)

换句话说,对于数组 char s[10]来说,数组名 s 和 &s 都是一样的(不信你可以自己写个程序试试)。在我们这个例子中,也就是说,都表示了偏移后的地址。这样,如果我们访问 指针的地址(或是成员变量的地址),那么也就不会让程序挂掉了。

正如下面的代码,可以运行一点也不会crash掉(你汇编一下你会看到用的都是lea指令):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct test{
int i;
short c;
char *p;
char s[10];
};

int main(){
struct test *pt=NULL;
printf("&s = %x\n", pt->s); //等价于 printf("%x\n", &(pt->s) );
printf("&i = %x\n", &pt->i); //因为操作符优先级,我没有写成&(pt->i)
printf("&c = %x\n", &pt->c);
printf("&p = %x\n", &pt->p);
return 0;
}

看到这里,你觉得这能算坑吗?不要出什么事都去怪语言,大家要想想是不是问题出在自己身上。

关于零长度的数组

首先,我们要知道,0长度的数组在ISO C和C++的规格说明书中是不允许的。这也就是为什么在VC++2012下编译你会得到一个警告:“arning C4200: 使用了非标准扩展 : 结构/联合中的零大小数组”。

那么为什么gcc可以通过而连一个警告都没有?那是因为gcc 为了预先支持C99的这种玩法,所以,让“零长度数组”这种玩法合法了。关于GCC对于这个事的文档在这里:“Arrays of Length Zero”,文档中给了一个例子(我改了一下,改成可以运行的了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdlib.h>
#include <string.h>

struct line {
int length;
char contents[0]; // C99的玩法是:char contents[]; 没有指定数组长度
};

int main(){
int this_length=10;
struct line *thisline = (struct line *)
malloc (sizeof (struct line) + this_length);
thisline->length = this_length;
memset(thisline->contents, 'a', this_length);
return 0;
}

上面这段代码的意思是:我想分配一个不定长的数组,于是我有一个结构体,其中有两个成员,一个是length,代表数组的长度,一个是contents,代码数组的内容。后面代码里的 this_length(长度是10)代表是我想分配的数据的长度。(这看上去是不是像一个C++的类?)这种玩法英文叫:Flexible Array,中文翻译叫:柔性数组。

我们来用gdb看一下:

1
2
3
4
5
6
7
8
(gdb) p thisline
$1 = (struct line *) 0x601010

(gdb) p *thisline
$2 = {length = 10, contents = 0x601010 "\n"}

(gdb) p thisline->contents
$3 = 0x601014 "aaaaaaaaaa"

我们可以看到:在输出*thisline时,我们发现其中的成员变量contents的地址居然和thisline是一样的(偏移量为0x0??!!)。但是当我们输出thisline->contents的时候,你又发现contents的地址是被offset了0x4了的,内容也变成了10个‘a’。(我觉得这是一个GDB的bug,VC++的调试器就能很好的显示)

我们继续,如果你sizeof(char[0])或是 sizeof(int[0]) 之类的零长度数组,你会发现sizeof返回了0,这就是说,零长度的数组是存在于结构体内的,但是不占结构体的size。你可以简单的理解为一个没有内容的占位标识,直到我们给结构体分配了内存,这个占位标识才变成了一个有长度的数组。

看到这里,你会说,为什么要这样搞啊,把contents声明成一个指针,然后为它再分配一下内存不行么?就像下面一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct line {
int length;
char *contents;
};

int main(){
int this_length=10;
struct line *thisline = (struct line *)malloc (sizeof (struct line));
thisline->contents = (char*) malloc(sizeof(char) * this_length);
thisline->length = this_length;
memset(thisline->contents, 'a', this_length);
return 0;
}

这不一样清楚吗?而且也没什么怪异难懂的东西。是的,这也是普遍的编程方式,代码是很清晰,也让人很容易理解。即然这样,那为什么要搞一个零长度的数组?有毛意义?!

这个事情出来的原因是——我们想给一个结构体内的数据分配一个连续的内存!这样做的意义有两个好处:

第一个意义是,方便内存释放。如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。(读到这里,你一定会觉得C++的封闭中的析构函数会让这事容易和干净很多)

第二个原因是,这样有利于访问速度。连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)

我们来看看是怎么个连续的,用gdb的x命令来查看:(我们知道,用struct line {}中的那个char contents[]不占用结构体的内存,所以,struct line就只有一个int成员,4个字节,而我们还要为contents[]分配10个字节长度,所以,一共是14个字节)

1
2
3
(gdb) x /14b thisline
0x601010: 10 0 0 0 97 97 97 97
0x601018: 97 97 97 97 97 97

从上面的内存布局我们可以看到,前4个字节是 int length,后10个字节就是char contents[]。

如果用指针的话,会变成这个样子:

1
2
3
4
5
6
(gdb) x /16b thisline
0x601010: 1 0 0 0 0 0 0 0
0x601018: 32 16 96 0 0 0 0 0
(gdb) x /10b this->contents
0x601020: 97 97 97 97 97 97 97 97
0x601028: 97 97

上面一共输出了四行内存,其中,

  • 第一行前四个字节是 int length,第一行的后四个字节是对齐。
  • 第二行是char* contents,64位系统指针8个长度,他的值是0x20 0x10 0x60 也就是0x601020。
  • 第三行和第四行是char* contents指向的内容。

从这里,我们看到,其中的差别——数组的原地就是内容,而指针的那里保存的是内容的地址

转载自 酷 壳 – CoolShell

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# printf("s: %s\n", st.s);
0x00005555555551c4 <+27>: lea -0x30(%rbp),%rax
0x00005555555551c8 <+31>: add $0x4,%rax
0x00005555555551cc <+35>: mov %rax,%rsi
0x00005555555551cf <+38>: lea 0xe2e(%rip),%rax # 0x555555556004
0x00005555555551d6 <+45>: mov %rax,%rdi
0x00005555555551d9 <+48>: mov $0x0,%eax
0x00005555555551de <+53>: call 0x555555555080 <printf@plt>
# printf("p: %s\n", st.p);
0x0000555555555221 <+120>: mov -0x20(%rbp),%rax
0x0000555555555225 <+124>: mov %rax,%rsi
0x0000555555555228 <+127>: lea 0xdf5(%rip),%rax # 0x555555556024
0x000055555555522f <+134>: mov %rax,%rdi
0x0000555555555232 <+137>: mov $0x0,%eax
0x0000555555555237 <+142>: call 0x555555555080 <printf@plt>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo...
(gdb) l 1
1 #include <iostream>
2 #include <stdio.h>
3
4 using namespace std;
5
6 struct String {
7 int len;
8 char s[10];
9 char *p;
10 int length;
(gdb)
11 };
12
13 int main() {
14 String st;
15 printf("s: %s\n", st.s);
16 printf("s ptr: %p\n", st.s);
17 printf("(&s) ptr: %p\n", &(st.s));
18 printf("p: %s\n", st.p);
19 st.p = st.s;
20 printf("p: %s\n", st.p);
(gdb)
21 return 0;
22 }
23
(gdb) r
Starting program: /home/qeuroal/tmp/demo
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at test_v2.cpp:13
13 int main() {
(gdb) disassemble
Dump of assembler code for function main():
0x00005555555551a9 <+0>: endbr64
0x00005555555551ad <+4>: push %rbp
0x00005555555551ae <+5>: mov %rsp,%rbp
0x00005555555551b1 <+8>: sub $0x30,%rsp
=> 0x00005555555551b5 <+12>: mov %fs:0x28,%rax
0x00005555555551be <+21>: mov %rax,-0x8(%rbp)
0x00005555555551c2 <+25>: xor %eax,%eax
0x00005555555551c4 <+27>: lea -0x30(%rbp),%rax
0x00005555555551c8 <+31>: add $0x4,%rax
0x00005555555551cc <+35>: mov %rax,%rsi
0x00005555555551cf <+38>: lea 0xe2e(%rip),%rax # 0x555555556004
0x00005555555551d6 <+45>: mov %rax,%rdi
0x00005555555551d9 <+48>: mov $0x0,%eax
0x00005555555551de <+53>: call 0x555555555080 <printf@plt>
0x00005555555551e3 <+58>: lea -0x30(%rbp),%rax
0x00005555555551e7 <+62>: add $0x4,%rax
0x00005555555551eb <+66>: mov %rax,%rsi
0x00005555555551ee <+69>: lea 0xe16(%rip),%rax # 0x55555555600b
0x00005555555551f5 <+76>: mov %rax,%rdi
0x00005555555551f8 <+79>: mov $0x0,%eax
0x00005555555551fd <+84>: call 0x555555555080 <printf@plt>
0x0000555555555202 <+89>: lea -0x30(%rbp),%rax
0x0000555555555206 <+93>: add $0x4,%rax
0x000055555555520a <+97>: mov %rax,%rsi
0x000055555555520d <+100>: lea 0xe02(%rip),%rax # 0x555555556016
0x0000555555555214 <+107>: mov %rax,%rdi
0x0000555555555217 <+110>: mov $0x0,%eax
0x000055555555521c <+115>: call 0x555555555080 <printf@plt>
0x0000555555555221 <+120>: mov -0x20(%rbp),%rax
0x0000555555555225 <+124>: mov %rax,%rsi
0x0000555555555228 <+127>: lea 0xdf5(%rip),%rax # 0x555555556024
0x000055555555522f <+134>: mov %rax,%rdi
0x0000555555555232 <+137>: mov $0x0,%eax
0x0000555555555237 <+142>: call 0x555555555080 <printf@plt>
0x000055555555523c <+147>: lea -0x30(%rbp),%rax
0x0000555555555240 <+151>: add $0x4,%rax
0x0000555555555244 <+155>: mov %rax,-0x20(%rbp)
0x0000555555555248 <+159>: mov -0x20(%rbp),%rax
0x000055555555524c <+163>: mov %rax,%rsi
0x000055555555524f <+166>: lea 0xdce(%rip),%rax # 0x555555556024
0x0000555555555256 <+173>: mov %rax,%rdi
0x0000555555555259 <+176>: mov $0x0,%eax
0x000055555555525e <+181>: call 0x555555555080 <printf@plt>
0x0000555555555263 <+186>: mov $0x0,%eax
0x0000555555555268 <+191>: mov -0x8(%rbp),%rdx
0x000055555555526c <+195>: sub %fs:0x28,%rdx
0x0000555555555275 <+204>: je 0x55555555527c <main()+211>
0x0000555555555277 <+206>: call 0x5555555550a0 <__stack_chk_fail@plt>
0x000055555555527c <+211>: leave
0x000055555555527d <+212>: ret
End of assembler dump.
(gdb) l 1
1 #include <iostream>
2 #include <stdio.h>
3
4 using namespace std;
5
6 struct String {
7 int len;
8 char s[10];
9 char *p;
10 int length;
(gdb)
11 };
12
13 int main() {
14 String st;
15 printf("s: %s\n", st.s);
16 printf("s ptr: %p\n", st.s);
17 printf("(&s) ptr: %p\n", &(st.s));
18 printf("p: %s\n", st.p);
19 st.p = st.s;
20 printf("p: %s\n", st.p);
(gdb)
21 return 0;
22 }
23
(gdb) diass
Undefined command: "diass". Try "help".
(gdb) disassemble
Dump of assembler code for function main():
0x00005555555551a9 <+0>: endbr64
0x00005555555551ad <+4>: push %rbp
0x00005555555551ae <+5>: mov %rsp,%rbp
0x00005555555551b1 <+8>: sub $0x30,%rsp
=> 0x00005555555551b5 <+12>: mov %fs:0x28,%rax
0x00005555555551be <+21>: mov %rax,-0x8(%rbp)
0x00005555555551c2 <+25>: xor %eax,%eax
0x00005555555551c4 <+27>: lea -0x30(%rbp),%rax
0x00005555555551c8 <+31>: add $0x4,%rax
0x00005555555551cc <+35>: mov %rax,%rsi
0x00005555555551cf <+38>: lea 0xe2e(%rip),%rax # 0x555555556004
0x00005555555551d6 <+45>: mov %rax,%rdi
0x00005555555551d9 <+48>: mov $0x0,%eax
0x00005555555551de <+53>: call 0x555555555080 <printf@plt>
0x00005555555551e3 <+58>: lea -0x30(%rbp),%rax
0x00005555555551e7 <+62>: add $0x4,%rax
0x00005555555551eb <+66>: mov %rax,%rsi
0x00005555555551ee <+69>: lea 0xe16(%rip),%rax # 0x55555555600b
0x00005555555551f5 <+76>: mov %rax,%rdi
0x00005555555551f8 <+79>: mov $0x0,%eax
0x00005555555551fd <+84>: call 0x555555555080 <printf@plt>
0x0000555555555202 <+89>: lea -0x30(%rbp),%rax
0x0000555555555206 <+93>: add $0x4,%rax
0x000055555555520a <+97>: mov %rax,%rsi
0x000055555555520d <+100>: lea 0xe02(%rip),%rax # 0x555555556016
0x0000555555555214 <+107>: mov %rax,%rdi
0x0000555555555217 <+110>: mov $0x0,%eax
0x000055555555521c <+115>: call 0x555555555080 <printf@plt>
0x0000555555555221 <+120>: mov -0x20(%rbp),%rax
0x0000555555555225 <+124>: mov %rax,%rsi
0x0000555555555228 <+127>: lea 0xdf5(%rip),%rax # 0x555555556024
0x000055555555522f <+134>: mov %rax,%rdi
0x0000555555555232 <+137>: mov $0x0,%eax
0x0000555555555237 <+142>: call 0x555555555080 <printf@plt>
0x000055555555523c <+147>: lea -0x30(%rbp),%rax
0x0000555555555240 <+151>: add $0x4,%rax
0x0000555555555244 <+155>: mov %rax,-0x20(%rbp)
0x0000555555555248 <+159>: mov -0x20(%rbp),%rax
0x000055555555524c <+163>: mov %rax,%rsi
0x000055555555524f <+166>: lea 0xdce(%rip),%rax # 0x555555556024
0x0000555555555256 <+173>: mov %rax,%rdi
0x0000555555555259 <+176>: mov $0x0,%eax
0x000055555555525e <+181>: call 0x555555555080 <printf@plt>
0x0000555555555263 <+186>: mov $0x0,%eax
0x0000555555555268 <+191>: mov -0x8(%rbp),%rdx
0x000055555555526c <+195>: sub %fs:0x28,%rdx
0x0000555555555275 <+204>: je 0x55555555527c <main()+211>
0x0000555555555277 <+206>: call 0x5555555550a0 <__stack_chk_fail@plt>
0x000055555555527c <+211>: leave
0x000055555555527d <+212>: ret
End of assembler dump.
(gdb) info line 15
Line 15 of "test_v2.cpp" starts at address 0x5555555551c4 <main()+27> and ends at 0x5555555551e3 <main()+58>.
(gdb) ni
0x00005555555551be 13 int main() {
(gdb)
0x00005555555551c2 13 int main() {
(gdb)
15 printf("s: %s\n", st.s);
(gdb) disassemble
Dump of assembler code for function main():
0x00005555555551a9 <+0>: endbr64
0x00005555555551ad <+4>: push %rbp
0x00005555555551ae <+5>: mov %rsp,%rbp
0x00005555555551b1 <+8>: sub $0x30,%rsp
0x00005555555551b5 <+12>: mov %fs:0x28,%rax
0x00005555555551be <+21>: mov %rax,-0x8(%rbp)
0x00005555555551c2 <+25>: xor %eax,%eax
=> 0x00005555555551c4 <+27>: lea -0x30(%rbp),%rax
0x00005555555551c8 <+31>: add $0x4,%rax
0x00005555555551cc <+35>: mov %rax,%rsi
0x00005555555551cf <+38>: lea 0xe2e(%rip),%rax # 0x555555556004
0x00005555555551d6 <+45>: mov %rax,%rdi
0x00005555555551d9 <+48>: mov $0x0,%eax
0x00005555555551de <+53>: call 0x555555555080 <printf@plt>
0x00005555555551e3 <+58>: lea -0x30(%rbp),%rax
0x00005555555551e7 <+62>: add $0x4,%rax
0x00005555555551eb <+66>: mov %rax,%rsi
0x00005555555551ee <+69>: lea 0xe16(%rip),%rax # 0x55555555600b
0x00005555555551f5 <+76>: mov %rax,%rdi
0x00005555555551f8 <+79>: mov $0x0,%eax
0x00005555555551fd <+84>: call 0x555555555080 <printf@plt>
0x0000555555555202 <+89>: lea -0x30(%rbp),%rax
0x0000555555555206 <+93>: add $0x4,%rax
0x000055555555520a <+97>: mov %rax,%rsi
0x000055555555520d <+100>: lea 0xe02(%rip),%rax # 0x555555556016
0x0000555555555214 <+107>: mov %rax,%rdi
0x0000555555555217 <+110>: mov $0x0,%eax
0x000055555555521c <+115>: call 0x555555555080 <printf@plt>
0x0000555555555221 <+120>: mov -0x20(%rbp),%rax
0x0000555555555225 <+124>: mov %rax,%rsi
0x0000555555555228 <+127>: lea 0xdf5(%rip),%rax # 0x555555556024
0x000055555555522f <+134>: mov %rax,%rdi
0x0000555555555232 <+137>: mov $0x0,%eax
0x0000555555555237 <+142>: call 0x555555555080 <printf@plt>
0x000055555555523c <+147>: lea -0x30(%rbp),%rax
0x0000555555555240 <+151>: add $0x4,%rax
0x0000555555555244 <+155>: mov %rax,-0x20(%rbp)
0x0000555555555248 <+159>: mov -0x20(%rbp),%rax
0x000055555555524c <+163>: mov %rax,%rsi
0x000055555555524f <+166>: lea 0xdce(%rip),%rax # 0x555555556024
0x0000555555555256 <+173>: mov %rax,%rdi
0x0000555555555259 <+176>: mov $0x0,%eax
0x000055555555525e <+181>: call 0x555555555080 <printf@plt>
0x0000555555555263 <+186>: mov $0x0,%eax
0x0000555555555268 <+191>: mov -0x8(%rbp),%rdx
0x000055555555526c <+195>: sub %fs:0x28,%rdx
0x0000555555555275 <+204>: je 0x55555555527c <main()+211>
0x0000555555555277 <+206>: call 0x5555555550a0 <__stack_chk_fail@plt>
0x000055555555527c <+211>: leave
0x000055555555527d <+212>: ret
End of assembler dump.
(gdb) info line 15
Line 15 of "test_v2.cpp" starts at address 0x5555555551c4 <main()+27> and ends at 0x5555555551e3 <main()+58>.
(gdb) info line 16
Line 16 of "test_v2.cpp" starts at address 0x5555555551e3 <main()+58> and ends at 0x555555555202 <main()+89>.
(gdb) info line 18
Line 18 of "test_v2.cpp" starts at address 0x555555555221 <main()+120> and ends at 0x55555555523c <main()+147>.
(gdb) disassemble
Dump of assembler code for function main():
0x00005555555551a9 <+0>: endbr64
0x00005555555551ad <+4>: push %rbp
0x00005555555551ae <+5>: mov %rsp,%rbp
0x00005555555551b1 <+8>: sub $0x30,%rsp
0x00005555555551b5 <+12>: mov %fs:0x28,%rax
0x00005555555551be <+21>: mov %rax,-0x8(%rbp)
0x00005555555551c2 <+25>: xor %eax,%eax
=> 0x00005555555551c4 <+27>: lea -0x30(%rbp),%rax
0x00005555555551c8 <+31>: add $0x4,%rax
0x00005555555551cc <+35>: mov %rax,%rsi
0x00005555555551cf <+38>: lea 0xe2e(%rip),%rax # 0x555555556004
0x00005555555551d6 <+45>: mov %rax,%rdi
0x00005555555551d9 <+48>: mov $0x0,%eax
0x00005555555551de <+53>: call 0x555555555080 <printf@plt>
0x00005555555551e3 <+58>: lea -0x30(%rbp),%rax
0x00005555555551e7 <+62>: add $0x4,%rax
0x00005555555551eb <+66>: mov %rax,%rsi
0x00005555555551ee <+69>: lea 0xe16(%rip),%rax # 0x55555555600b
0x00005555555551f5 <+76>: mov %rax,%rdi
0x00005555555551f8 <+79>: mov $0x0,%eax
0x00005555555551fd <+84>: call 0x555555555080 <printf@plt>
0x0000555555555202 <+89>: lea -0x30(%rbp),%rax
0x0000555555555206 <+93>: add $0x4,%rax
0x000055555555520a <+97>: mov %rax,%rsi
0x000055555555520d <+100>: lea 0xe02(%rip),%rax # 0x555555556016
0x0000555555555214 <+107>: mov %rax,%rdi
0x0000555555555217 <+110>: mov $0x0,%eax
0x000055555555521c <+115>: call 0x555555555080 <printf@plt>
0x0000555555555221 <+120>: mov -0x20(%rbp),%rax
0x0000555555555225 <+124>: mov %rax,%rsi
0x0000555555555228 <+127>: lea 0xdf5(%rip),%rax # 0x555555556024
0x000055555555522f <+134>: mov %rax,%rdi
0x0000555555555232 <+137>: mov $0x0,%eax
0x0000555555555237 <+142>: call 0x555555555080 <printf@plt>
0x000055555555523c <+147>: lea -0x30(%rbp),%rax
0x0000555555555240 <+151>: add $0x4,%rax
0x0000555555555244 <+155>: mov %rax,-0x20(%rbp)
0x0000555555555248 <+159>: mov -0x20(%rbp),%rax
0x000055555555524c <+163>: mov %rax,%rsi
0x000055555555524f <+166>: lea 0xdce(%rip),%rax # 0x555555556024
0x0000555555555256 <+173>: mov %rax,%rdi
0x0000555555555259 <+176>: mov $0x0,%eax
0x000055555555525e <+181>: call 0x555555555080 <printf@plt>
0x0000555555555263 <+186>: mov $0x0,%eax
0x0000555555555268 <+191>: mov -0x8(%rbp),%rdx
0x000055555555526c <+195>: sub %fs:0x28,%rdx
0x0000555555555275 <+204>: je 0x55555555527c <main()+211>
0x0000555555555277 <+206>: call 0x5555555550a0 <__stack_chk_fail@plt>
0x000055555555527c <+211>: leave
0x000055555555527d <+212>: ret
End of assembler dump.
(gdb) f
#0 main () at test_v2.cpp:15
15 printf("s: %s\n", st.s);
(gdb) l 1
1 #include <iostream>
2 #include <stdio.h>
3
4 using namespace std;
5
6 struct String {
7 int len;
8 char s[10];
9 char *p;
10 int length;
(gdb)
11 };
12
13 int main() {
14 String st;
15 printf("s: %s\n", st.s);
16 printf("s ptr: %p\n", st.s);
17 printf("(&s) ptr: %p\n", &(st.s));
18 printf("p: %s\n", st.p);
19 st.p = st.s;
20 printf("p: %s\n", st.p);
(gdb)
21 return 0;
22 }
23
(gdb) p st.s
$1 = "\377\177\000\000\352\347\321\367\377\177"
(gdb) p st.p
$2 = 0x7ffff7e290c8 <std::wcout+8> "\370\063\342\367\377\177"
(gdb) p &st.p
$3 = (char **) 0x7fffffffe150
(gdb) p &st.s
$4 = (char (*)[10]) 0x7fffffffe144
(gdb) x /10xb s
No symbol "s" in current context.
(gdb) x /10xb st.s
0x7fffffffe144: 0xff 0x7f 0x00 0x00 0xea 0xe7 0xd1 0xf7
0x7fffffffe14c: 0xff 0x7f
(gdb) x /10xb st.p
0x7ffff7e290c8 <_ZSt5wcout+8>: 0xf8 0x33 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7ffff7e290d0 <_ZSt5wcout+16>: 0x06 0x00
(gdb) x /10xb &st.p
0x7fffffffe150: 0xc8 0x90 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe158: 0x88 0x8e
(gdb) x /10xx &st.p
0x7fffffffe150: 0xc8 0x90 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe158: 0x88 0x8e
(gdb) x /10xb &st.p
0x7fffffffe150: 0xc8 0x90 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe158: 0x88 0x8e
(gdb) p st
$5 = {len = -136147112, s = "\377\177\000\000\352\347\321\367\377\177", p = 0x7ffff7e290c8 <std::wcout+8> "\370\063\342\367\377\177", length = -136147320}
(gdb) p &st
$6 = (String *) 0x7fffffffe140
(gdb) p &st.
len length p s
(gdb) p &st.
len length p s
(gdb) p &st.len
$7 = (int *) 0x7fffffffe140
(gdb) p &st.s
$8 = (char (*)[10]) 0x7fffffffe144
(gdb) p &st.p
$9 = (char **) 0x7fffffffe150
(gdb) p &st.length
$10 = (int *) 0x7fffffffe158
(gdb) x /10xb st
Value can't be converted to integer.
(gdb) x /10xb &st
0x7fffffffe140: 0x58 0x8f 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe148: 0xea 0xe7
(gdb) p &st
$11 = (String *) 0x7fffffffe140
(gdb) x /32xb
0x7fffffffe14a: 0xd1 0xf7 0xff 0x7f 0x00 0x00 0xc8 0x90
0x7fffffffe152: 0xe2 0xf7 0xff 0x7f 0x00 0x00 0x88 0x8e
0x7fffffffe15a: 0xe2 0xf7 0xff 0x7f 0x00 0x00 0x40 0x89
0x7fffffffe162: 0xe2 0xf7 0xff 0x7f 0x00 0x00 0x00 0x18
(gdb) x /32xb &st
0x7fffffffe140: 0x58 0x8f 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe148: 0xea 0xe7 0xd1 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe150: 0xc8 0x90 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe158: 0x88 0x8e 0xe2 0xf7 0xff 0x7f 0x00 0x00
(gdb) p /x st.p
$12 = 0x7ffff7e290c8
(gdb) p /x st.len
$13 = 0xf7e28f58
(gdb) p /x st.s
$14 = {0xff, 0x7f, 0x0, 0x0, 0xea, 0xe7, 0xd1, 0xf7, 0xff, 0x7f}
(gdb) p st.s
$15 = "\377\177\000\000\352\347\321\367\377\177"
(gdb) p st.s /x
No symbol "x" in current context.
(gdb) p /x st.s
$16 = {0xff, 0x7f, 0x0, 0x0, 0xea, 0xe7, 0xd1, 0xf7, 0xff, 0x7f}
(gdb) p /x &st.p
$17 = 0x7fffffffe150
(gdb) x /64xb &st
0x7fffffffe140: 0x58 0x8f 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe148: 0xea 0xe7 0xd1 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe150: 0xc8 0x90 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe158: 0x88 0x8e 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe160: 0x40 0x89 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe168: 0x00 0x18 0xaf 0x66 0xb1 0xde 0xe5 0xa6
0x7fffffffe170: 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe178: 0x90 0x9d 0x82 0xf7 0xff 0x7f 0x00 0x00
(gdb) l 1
1 #include <iostream>
2 #include <stdio.h>
3
4 using namespace std;
5
6 struct String {
7 int len;
8 char s[10];
9 char *p;
10 int length;
(gdb) p /x len
No symbol "len" in current context.
(gdb) p /x st.len
$18 = 0xf7e28f58
(gdb) p /x s
No symbol "s" in current context.
(gdb) p /x st.x
There is no member named x.
(gdb) p /x st.s
$19 = {0xff, 0x7f, 0x0, 0x0, 0xea, 0xe7, 0xd1, 0xf7, 0xff, 0x7f}
(gdb) p /x st.p
$20 = 0x7ffff7e290c8
(gdb) p /x st.p
$21 = 0x7ffff7e290c8
(gdb) p /x st.p
$22 = 0x7ffff7e290c8
(gdb) p /x st.s
$23 = {0xff, 0x7f, 0x0, 0x0, 0xea, 0xe7, 0xd1, 0xf7, 0xff, 0x7f}
(gdb) set st.p=st.s
(gdb) x /64xb &st
0x7fffffffe140: 0x58 0x8f 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe148: 0xea 0xe7 0xd1 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe150: 0x44 0xe1 0xff 0xff 0xff 0x7f 0x00 0x00
0x7fffffffe158: 0x88 0x8e 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe160: 0x40 0x89 0xe2 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe168: 0x00 0x18 0xaf 0x66 0xb1 0xde 0xe5 0xa6
0x7fffffffe170: 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe178: 0x90 0x9d 0x82 0xf7 0xff 0x7f 0x00 0x00
(gdb) p /x st.p
$24 = 0x7fffffffe144
(gdb) p /x &st.s
$25 = 0x7fffffffe144
(gdb) p /x *st.p
$26 = 0xff
(gdb) p /x st.length
$27 = 0xf7e28e88
(gdb) set st.p = &st.s
(gdb) p /x st.p
$28 = 0x7fffffffe144
(gdb) q
A debugging session is active.

Inferior 1 [process 29494] will be killed.

Quit anyway? (y or n) y