Skip to content

Commit

Permalink
linux-c笔记调整(gdb, pointer)
Browse files Browse the repository at this point in the history
  • Loading branch information
whitestarrain committed Nov 3, 2023
1 parent 2ad1913 commit f7d909f
Showing 1 changed file with 94 additions and 23 deletions.
117 changes: 94 additions & 23 deletions C/c.md
Original file line number Diff line number Diff line change
Expand Up @@ -4719,36 +4719,66 @@ Program received signal SIGSEGV, Segmentation fault.

### 1.10.5. 命令整理

- 基本
- backtrace(或 bt) 查看各级函数调用及参数
- finish 连续运行到当前函数返回为止,然后停下来等待命令
- frame(或 f) 帧编号 选择栈帧
- info(或 i) locals 查看当前栈帧局部变量的值
- 源码&汇编码
- list(或 l) 列出源代码,接着上次的位置往下列,每次列 10 行
- list 行号 列出从第几行开始的源代码
- list 函数名 列出某个函数的源代码

- 运行
- start 开始执行程序,停在 main 函数第一行语句前面等待命令
- next(或 n) 执行下一行语句
- print(或 p) 打印表达式的值,通过表达式可以修改变量的值或者调用函数
- step(或 s) 执行下一行语句,如果有函数调用则进入到函数中
- finish 连续运行到当前函数返回为止,然后停下来等待命令
- continue(或 c) 从当前位置开始连续运行程序,直到断点
- quit(或 q) 退出 gdb 调试环境
- run(或 r) 从头重新开始运行程序, restart and debug

- 变量
- print(或 p) 打印表达式的值,通过表达式可以修改变量的值或者调用函数
- set var 修改变量的值
- start 开始执行程序,停在 main 函数第一行语句前面等待命令
- step(或 s) 执行下一行语句,如果有函数调用则进入到函数中
- display 变量名 跟踪查看某个变量,每次停下来都显示它的值

- 状态
- backtrace(或 bt) 查看各级函数调用及参数
- frame(或 f) 帧编号 选择栈帧
- info(或 i) locals 查看当前栈帧局部变量的值

- 跟踪
- undisplay 跟踪显示号 取消跟踪显示

- 断点
- break(或 b) 行号 在某一行设置断点
- break 函数名 在某个函数开头设置断点
- break ... if ... 设置条件断点
- continue(或 c) 从当前位置开始连续运行程序
- delete breakpoints 断点号 删除断点
- display 变量名 跟踪查看某个变量,每次停下来都显示它的值
- disable breakpoints 断点号 禁用断点
- enable 断点号 启用断点
- info(或 i) breakpoints 查看当前设置了哪些断点
- run(或 r) 从头开始连续运行程序
- undisplay 跟踪显示号 取消跟踪显示
- 其他

- 观察点:监视指定地址的变化
- watch 设置观察点
- info(或 i) watchpoints 查看当前设置了哪些观察点

- 其他
- x 从某个位置开始打印存储单元的内容,全部当成字节来看,而不区分哪个字节属于哪个变量
- `x/{format}`
- 文档:[x command](https://visualgdb.com/gdbreference/commands/x): 使用指定的格式显示给定地址的内存内容

- 汇编级别(来自后续章节)
- `disassemble` 可以反汇编当前函数或者指定的函数,
- 单独用 `disassemble` 命令是反汇编当前函数,
- 如果 `disassemble` 命令后面跟函数名或地址则反汇编指定的函数。
- `si` 命令可以一条指令一条指令地单步调试
- `info registers` 可以显示所有寄存器的当前值。
- 在 `gdb` 中表示寄存器名时前面要加个 `$`,
- 例如 `p $esp` 可以打印 `esp` 寄存器的值,
- `esp` 寄存器的值是 `0xbff1c3f4`,所以 `x/20 $esp` 命令查看内存中从 `0xbff1c3f4` 地址开始的 20 个 32 位数。

- 在执行程序时,操作系统为进程分配一块栈空间来保存函数栈帧,
- **`esp` 寄存器总是指向栈顶**
> 64位系统上,为`rsp`,见 《深入理解操作系统》p120
- 在 x86 平台上这个栈是从高地址向低地址增长的,
- 我们知道每次调用一个函数都要分配一个栈帧来保存参数和局部变量

## 1.11. 排序与查找

Expand Down Expand Up @@ -7469,9 +7499,11 @@ $ hexdump -C max.o

##### .bss

- C 语言的全局变量如果在代码中没有初始化,就会在程序加载时用 0 初始化。这种数据属于 `.bss` 段,
- 在加载时它和 `.data` 段一样都是可读可写的数据,但是在 ELF 文件中 `.data` 段需要占用一部分空间保存初始值,而 `.bss` 段则不需要。
- 也就是说,`.bss` 段在文件中只占一个 Section Header 而没有对应的 Section,程序加载时 `.bss` 段占多大内存空间在 Section Header 中描述。
- C 语言的 **全局变量如果在代码中没有初始化,就会在程序加载时用 0 初始化** 。这种数据属于 `.bss` 段,
- 在加载时它和 `.data` 段一样都是可读可写的数据, **会实际分配内存空间**
- 但是在 ELF 文件中 `.data` 段需要占用一部分空间保存初始值,而 `.bss` 段则不需要
- 也就是说,`.bss` 段在文件中只占一个 Section Header 而没有对应的 Section,
- 程序加载时 `.bss` 段占多大内存空间在 Section Header 中描述。
- 在我们这个例子中没有用到 `.bss` 段,在[第 19 章「汇编与 C 之间的关系」第 3 节「变量的存储布局」]()会看到这样的例子。

##### .rel.text 和 .symtab
Expand Down Expand Up @@ -13829,7 +13861,11 @@ int main(void)

### 2.11.2. 传入参数与传出参数

如果函数接口有指针参数,既可以把指针所指向的数据传给函数使用(称为传入参数),也可以由函数填充指针所指的内存空间,传回给调用者使用(称为传出参数),例如 `strcpy` 的 `src` 参数是传入参数,`dest` 参数是传出参数。有些函数的指针参数同时担当了这两种角色,如 `select(2)` 的 `fd_set *` 参数,既是传入参数又是传出参数,这称为 Value-result 参数。
如果函数接口有指针参数,
- 既可以把指针所指向的数据传给函数使用(称为 **传入参数** ),
- 也可以由函数填充指针所指的内存空间,传回给调用者使用(称为 **传出参数** )
- 例如 `strcpy` 的 `src` 参数是传入参数,`dest` 参数是传出参数。
- 有些函数的指针参数同时担当了这两种角色,如 `select(2)` 的 `fd_set *` 参数,既是传入参数又是传出参数,这称为 **Value-result 参数** 。

<p id="t24-1">表 24.1. 传入参数示例:`void func(const unit_t \*p);`</p>

Expand Down Expand Up @@ -13895,7 +13931,17 @@ int main(void)
}
```

很多系统函数对于指针参数是 `NULL` 的情况有特殊规定:如果传入参数是 `NULL` 表示取缺省值,例如 `pthread_create(3)` 的 `pthread_attr_t *` 参数,也可能表示不做特别处理,例如 `free` 的参数;如果传出参数是 `NULL` 表示调用者不需要传出值,例如 `time(2)` 的参数。这些特殊规定应该在文档中写清楚。
很多系统函数对于指针参数是 `NULL` 的情况有特殊规定:

- 如果传入参数是 `NULL`
- 表示取缺省值,
- 例如 `pthread_create(3)` 的 `pthread_attr_t *` 参数,
- 也可能表示不做特别处理,
- 例如 `free` 的参数;
- 如果传出参数是 `NULL` 表示调用者不需要传出值,
- 例如 `time(2)` 的参数。

这些特殊规定应该在文档中写清楚。

### 2.11.3. 两层指针的参数

Expand All @@ -13913,7 +13959,11 @@ extern void get_a_day(const char **);
#endif
```

想一想,这里的参数指针是 `const char **`,有 `const` 限定符,却不是传入参数而是传出参数,为什么?如果是传入参数应该怎么表示?
想一想,这里的参数指针是 `const char **`,有 `const` 限定符,却不是传入参数而是传出参数,为什么?

- `const char **` 指 dereference 两次后的的值(`**ptr`)不能修改, 但 dereference 一次(`*ptr`)后的值可以修改,修改后`**ptr`取值就变了
- 如果是传入参数,可以定义为: `const char const * const *`


```c
/* redirect_ptr.c */
Expand Down Expand Up @@ -13942,6 +13992,8 @@ int main(void)
}
```

> 二层指针其实就是 **指针的传地址** ,在c++中,方法也可以声明成 `void get_a_day(const char* &pp)`

两层指针作为传出参数还有一种特别的用法,可以在函数中分配内存,调用者通过传出参数取得指向该内存的指针,比如 `getaddrinfo(3)` 的 `struct addrinfo **` 参数。一般来说,实现一个分配内存的函数就要实现一个释放内存的函数,所以 `getaddrinfo(3)` 有一个对应的 `freeaddrinfo(3)` 函数。

<p id="t24-4">表 24.4. 通过参数分配内存示例:`void alloc_unit(unit_t \**pp);` `void free_unit(unit_t \*p);`</p>
Expand Down Expand Up @@ -14008,11 +14060,24 @@ int main(void)

思考一下,为什么在 `main` 函数中不能直接调用 `free(p)` 释放内存,而要调用 `free_unit(p)`?为什么一层指针的函数接口 `void alloc_unit(unit_t *p);` 不能分配内存,而一定要用两层指针的函数接口?

总结一下,两层指针参数如果是传出的,可以有两种情况:第一种情况,传出的指针指向静态内存(比如上面的例子),或者指向已分配的动态内存(比如指向某个链表的节点);第二种情况是在函数中动态分配内存,然后传出的指针指向这块内存空间,这种情况下调用者应该在使用内存之后调用释放内存的函数,调用者的责任是请求分配和请求释放内存,实现者的责任是完成分配内存和释放内存的操作。由于这两种情况的函数接口相同,应该在文档中说明是哪一种情况。
总结一下,两层指针参数如果是传出的,可以有两种情况:

- 第一种情况,传出的指针指向静态内存(比如上面的例子),或者指向已分配的动态内存(比如指向某个链表的节点);
- 第二种情况是在函数中动态分配内存,然后传出的指针指向这块内存空间,
- 这种情况下调用者应该在使用内存之后调用释放内存的函数,
- 调用者的责任是请求分配和请求释放内存,实现者的责任是完成分配内存和释放内存的操作。

由于这两种情况的函数接口相同,应该在文档中说明是哪一种情况。

### 2.11.4. 返回值是指针的情况

返回值显然是传出的而不是传入的,如果返回值传出的是指针,和上一节通过参数传出指针类似,也分为两种情况:第一种是传出指向静态内存或已分配的动态内存的指针,例如 `localtime(3)` 和 `inet_ntoa(3)`,第二种是在函数中动态分配内存并传出指向这块内存的指针,例如 `malloc(3)`,这种情况通常还要实现一个释放内存的函数,所以有和 `malloc(3)` 对应的 `free(3)`。由于这两种情况的函数接口相同,应该在文档中说明是哪一种情况。
返回值显然是传出的而不是传入的,如果返回值传出的是指针,和上一节通过参数传出指针类似,也分为两种情况:

- 第一种是传出指向静态内存或已分配的动态内存的指针,例如 `localtime(3)` 和 `inet_ntoa(3)`
- 第二种是在函数中动态分配内存并传出指向这块内存的指针,
- 例如 `malloc(3)`,这种情况通常还要实现一个释放内存的函数,所以有和 `malloc(3)` 对应的 `free(3)`。

由于这两种情况的函数接口相同,应该在文档中说明是哪一种情况

<p id="t24-5">表 24.5. 返回指向已分配内存的指针示例:`unit_t \*func(void);`</p>

Expand Down Expand Up @@ -14056,7 +14121,13 @@ int main(void)
}
```

这个程序的运行结果是 `Sunday Monday` 吗?请读者自己分析一下。
这个程序的运行结果是 `Sunday Monday` 吗?

- 不是,static类型的变量程序运行时就会分配,
- get_a_day调用两次后,最后buf指向的地址会变为一个值。
- 调用顺序由编译器决定
- gcc下,最后会打印两个 `Sunday`
- 详细原理可见 **Side Effect 与 Sequence Point** 一节

<p id="t24-6">表 24.6. 动态分配内存并返回指针示例:`unit_t \*alloc_unit(void); void free_unit(unit_t \*p);`</p>

Expand Down Expand Up @@ -14125,7 +14196,7 @@ int main(void)

### 2.11.5. 回调函数

如果参数是一个函数指针,调用者可以传递一个函数的地址给实现者,让实现者去调用它,这称为回调函数(Callback Function)。例如`qsort(3)`和`bsearch(3)`。
如果参数是一个函数指针,调用者可以传递一个函数的地址给实现者,让实现者去调用它,这称为 **回调函数(Callback Function)** 。例如`qsort(3)`和`bsearch(3)`。

<p id="t24-7">表 24.7. 回调函数示例:`void func(void (\*f)(void \*), void \*p);`</p>

Expand Down

0 comments on commit f7d909f

Please sign in to comment.