%% # 纲要 > 主干纲要、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