问
1struct Book {
2 char name[10];
3 char type[10];
4 int price;
5};
6
7struct Book *getBook()
8{
9 struct Book b = {
10 .name = "C Primer",
11 .type = "Programme",
12 .price = 100,
13 };
14 return &b;
15};
16
17int main()
18{
19 struct Book *b = getBook();
20 char *name = b->name;
21 char *type = b->type;
22 int price = b->price;
23
24 printf("name = %s\n", name);
25 printf("type = %s\n", type);
26 printf("price = %d\n", price);
27
28 free(b);
29}
代码的 main
函数中,会发生指针访问错误的语句有哪些?(多选题)
struct Book *b = get_struct();
char *name = b->name;
char *type = b->type;
int price = b->price;
printf("name = %s\n", name);
printf("type = %s\n", type);
printf("price = %d\n", price);
free(b);
答
4、5、6、8
解答:
“b 返回的是一个错误地址,偏移后的
b->price
的地址也是错误的,根据这个地址去访存的时候就会出错”“那 2 和 3 为什么不是错的?”
“因为
b->name
和b->type
计算完偏移地址后就直接赋值了,即使地址是错的,但是不会去访存”
这里主要是因为 name
和 type
是 char [10]
类型。用同样的字符串初始化,其保存内容的方式与 char *
不同。
字符数组
1b, b->name ──►┌──────────┐
2 │ │
3 │ │
4 │ char[10] │
5 │ │
6 │ │
7 b->type ──►├──────────┤
8 │ │
9 │ │
10 │ char[10] │
11 │ │
12 │ │
13 ├──────────┤
14 b->price = │ int │
15 └──────────┘
数组所声明的内存空间都会被纳入结构体中,结构体的体积也会因此变得“硕大无比”。整个数组空间都在栈上,初始化时,初始值会被加载到栈区的指定位置上。
1; struct Book b = { ... };
2 movabs rax, 8243114992628408387
3 mov QWORD PTR [rbp-48], rax
4 mov WORD PTR [rbp-40], 0
5 movabs rax, 7885065666585129552
6 mov QWORD PTR [rbp-38], rax
7 mov WORD PTR [rbp-30], 101
8 mov DWORD PTR [rbp-28], 100
因此 b->name
和 b->type
的值只要对 b
加上相应的偏移就可以获取,不需要对 b
进行解引用("*
")操作。
1; // b is stored at [rbp-8]
2; char *name = b->name;
3 mov rax, QWORD PTR [rbp-8] ; b and b->name points to the same place
4 mov QWORD PTR [rbp-16], rax ; so just store it unchanged
5; char *type = b->type;
6 mov rax, QWORD PTR [rbp-8] ; get b
7 add rax, 10 ; shift b by 10 bytes (get b->type)
8 mov QWORD PTR [rbp-24], rax ; store b+10 (as a local variable)
9; int price = b->price;
10 mov rax, QWORD PTR [rbp-8]
11 mov eax, DWORD PTR [rax+20]
12 mov DWORD PTR [rbp-28], eax
字符指针
1 ┌──────────────┐
2 ┌──►│ "C Primer" │
3 ┌──────────┐ │ └──────────────┘
4 b->name = │ char * ├───┘
5 ├──────────┤ ┌───────────────┐
6 b->type = │ char * ├──────►│ "Programme" │
7 ├──────────┤ └───────────────┘
8b->price = │ int │
9 └──────────┘
结构体中只保存指针,字符串内容则保存在程序的数据段中。初始化的过程则是将指针指向字符串的首地址。
1.LC0:
2 .string "C Primer"
3.LC1:
4 .string "Programme"
5
6; struct Book b = { ... };
7 mov QWORD PTR [rbp-32], OFFSET FLAT:.LC0
8 mov QWORD PTR [rbp-24], OFFSET FLAT:.LC1
9 mov DWORD PTR [rbp-16], 100
这种情况下,获取 b->name
和 b->type
就需要对 b
及其偏移后的指针进行解引用。
1; char *name = b->name;
2 mov rax, QWORD PTR [rbp-8] ; get value of b->name
3 mov rax, QWORD PTR [rax] ; dereference b->name
4 mov QWORD PTR [rbp-16], rax ; save what it gets as a local variable
5; char *type = b->type;
6 mov rax, QWORD PTR [rbp-8]
7 mov rax, QWORD PTR [rax+8] ; b+8 to get b->type and dereference
8 mov QWORD PTR [rbp-24], rax
9; int price = b->price;
10 mov rax, QWORD PTR [rbp-8]
11 mov eax, DWORD PTR [rax+16]
12 mov DWORD PTR [rbp-28], eax
因此,若 struct Book
中的 name
和 type
改为字符指针,则 2 和 3 也会变得和 4 一样要去访问内存,执行也会出错。
参考链接
以上汇编结果来自:Compiler Explorer 。