%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# 可变参数
`<stdarg.h>` 中声明了一种类型 `va_list` 作为**可变参数类型**,同时提供了几个**宏**进行支持—— `va_start`, `va_arg`, `va_end`,`va_copy`
![[_attachment/02-开发笔记/02-c/c-函数相关.assets/IMG-c-函数相关-E5AD649FFB0DF66B20201660D1001AEE.png|304]]
- **`va_start(va_list ap, last)`**:**初始化 `ap`**——将**可变参数**存入 **`va_list` 类型的变量 ap 中**,第二参数指出**可变参数==在参数 `last` 之后==**
- **`va_arg(va_list ap, type)`**:获取可变参数列表中的**下一个参数**,其中 `type` 为**实参类型**,必须匹配实参。
- **`va_end(va_list ap)`**:结束对 `ap`中 "**结束可变参数**" 的访问;
- **`va_copy(va_list dest, va_list src)`**: 将 `src` 中的可变参数拷贝到 `dest` 中。
> [!caution] 带有可变数量参数的函数必须至少有一个 “常规“参数;省略号总是出现在形式参数列表的末尾,在最后一个正常参数的后边。
> [!caution] 将 `va_list` 传值并在在多个函数中分别获取参数是 "未定义行为" !
> 将 `va_list` 类型作为 "参数" 传递时,"可能"传递的是 "副本"——若是如此,意味着对副本的操作(例如通过 `va_arg` 读取参数)不会影响原始 `va_list` 的状态。
> 为此,即使一个函数消耗了部分参数,另一个函数可能会从头开始重新读取这些参数,导致获取到的是意料之外的值。
>
> 参见:[c - Is GCC mishandling a pointer to a va\_list passed to a function? - Stack Overflow](https://stackoverflow.com/questions/8047362/is-gcc-mishandling-a-pointer-to-a-va-list-passed-to-a-function)
>
> [!caution] `va_list` 通常不能对其 "取地址`&`" 并传递给接收 `va_list*` 的形成
>
> `va_list` 是个别名,其**具体类型可能是数组、结构体**(取决于具体实现)。
> 因此对 `va_list` 类型对象 `ap` 取地址 `&ap` 得到的是 "**指向具体类型的指针**" 而不是**指向 `va_list` 类型**的指针。
> 例如,当 `va_list` 是数组类型时,`&ap` 得到**指向指针的指针**。
>
> 参见:[c - Is GCC mishandling a pointer to a va\_list passed to a function? - Stack Overflow](https://stackoverflow.com/questions/8047362/is-gcc-mishandling-a-pointer-to-a-va-list-passed-to-a-function)
>
使用示例:
```c
#include <stdarg.h>
// 首个参数n指定 "后续参数个数", `...`接收可变数量的参数
void print_int(int n, ...) {
va_list ap; // 可变参数类型的变量
va_start(ap, n); // 初始化args
for (int i = 0; i < n; ++i) {
printf("%d ", va_arg(ap, int)); // 依次获取各个可变参数
}
printf("\n");
va_end(ap); // 结束对ap中可变参数的访问
}
```
<br><br>
# 以 `void*` 作为函数参数或返回类型
`void*` 指针**可指向任何类型的数据**,以 `void*` 作为函数参数或返回类型,则可接收 "**==任意类型==的指针**",返回 "**==任意类型==的数据**"。
> [!caution] `void*` **不提供任何关于指向的数据类型的信息**。因此,在**使用指针时需==手动地明确转换为正确的类型==**。
> [!info] POSIX 库中的 `pthread_create()` 中的 "函数指针"参数,就声明了函数参数与返回类型均为 `void*`
>
> ![[_attachment/02-开发笔记/02-c/c-函数相关.assets/IMG-c-函数相关-652260E7C19CDBE222027B89FE384F6D.png|684]]
>
> [!example] 示例:通用的内存复制函数
>
> ```c
> void memoryCopy(void* dest, const void* src, size_t n) {
> char* d = (char*) dest; // void*转为char*, 以字节为单位进行拷贝
> const char* s = (const char*) src;
> for (size_t i = 0; i < n; i++) {
> d[i] = s[i];
> }
> }
> ```
>
>
> [!example] 示例:通用的动态内存分配
>
> 以 `void*` 作为返回类型,**可分配不同类型的数据**,如 **`int`, `double` 或任何自定义类型**。
>
> ```c
> void* createArray(size_t elementSize, size_t elementCount) {
> return malloc(elementSize * elementCount);
> }
> ```
>
>
<br><br><br>
# `restrict` 关键字含义
函数形参中的 `restrict` 关键字用以修饰 "**==指针类型==**",指示编译器:**该指针所指向的内存地址仅能通过该指针访问**,即不会被其他任何指针所访问(例如**不会有另一个指针所指区域与其重叠**),从而**允许编译器进行优化**。
> [!example]
> 示例:
>
> ```c
> // 对两个指针src与dest添加`restrict`关键字, 指示编译器, 可假定两指针所指向的内存区域不会重叠,从而可进行优化.
> void copy(int* restrict dest, const int* restrict src, int n) {
> for (int i = 0; i < n; ++i) {
> dest[i] = src[i];
> }
> }
> ```
>
>
<br><br><br>
# 参考资料
# Footnotes