自定义node_t类型可变参数C函数多参数时段错误问题
兄弟,先看你代码里最扎眼的问题:你重复定义了add_node函数!同一个编译单元里两个签名完全一样的函数,C语言绝对不允许这种操作——要么编译报错,要么编译器偷偷覆盖掉前面的定义,导致你第一个函数里的类型判断逻辑根本没被执行,直接跑了第二个简化版的函数,这本身就是崩溃的潜在诱因。
接下来咱们逐个解决问题:
1. 先干掉重复定义的函数
删掉第二个add_node,只保留带switch类型判断的那个(毕竟这才符合你按类型存数组的需求)。
2. 可变参数必须加终止标记
你调用add_node的时候没传NULL结尾啊!可变参数函数本身不知道你传了多少个参数,必须手动加一个NULL当“结束符”,不然va_arg会一直读栈上的垃圾数据,读到非法内存直接就段错误了。所以调用的时候要改成:
add_node(mp, ROOT_NODE, NODE_A, NODE_B, NODE_C, NULL);
3. 循环逻辑的坑
你第一个函数的do-while循环逻辑有问题:
- 先取了第一个参数处理,然后
while里又取一次下一个参数,判断满足条件才进入下一次循环。但如果没传NULL,最后会读到垃圾值,直接崩。 - 第二个函数更坑,直接把第一次取的参数丢了,根本没处理第一个传入的节点(NODE_A)。
修正后的循环应该直接遍历每个参数,直到遇到NULL,同时跳过和目标节点key相同的节点:
node_t *n; while ((n = va_arg(lmnts, node_t *)) != NULL) { if (apr_strnatcmp(n->key, p_target_node->key) == 0) { continue; } // 处理节点... }
4. 数组push的类型转换
你原来写的*(const node_t**)apr_array_push(tbl) = n;里的const没必要,反而容易出问题,直接写成*(node_t**)apr_array_push(tbl) = n;就行,因为apr_array_push返回的是指向数组新元素的void*,强转成node_t**后赋值即可。
修复后的完整代码
下面是修正后的完整代码,我还加了点错误处理,比如遇到未知类型的节点会打印错误并跳过:
#include <stdarg.h> #include <stdio.h> #include <apr_general.h> #include <apr_hash.h> #include <apr_pools.h> #include <apr_strings.h> #include <apr_tables.h> typedef enum node_type_t { node_type_string, node_type_number, } node_type_t; typedef struct node_t { char *key; node_type_t type; apr_array_header_t *arr_strings; apr_array_header_t *arr_numbers; } node_t; node_t *create_node(apr_pool_t *p_pool, char *p_key, node_type_t p_type) { node_t *NODE = apr_palloc(p_pool, sizeof(node_t)); NODE->key = p_key; NODE->type = p_type; // 初始化数组时给点初始容量,避免频繁扩容 NODE->arr_strings = apr_array_make(p_pool, 2, sizeof(node_t*)); NODE->arr_numbers = apr_array_make(p_pool, 2, sizeof(node_t*)); return NODE; } int add_node(apr_pool_t *p_pool, node_t *p_target_node, ...) { int rv = 0; va_list lmnts; va_start(lmnts, p_target_node); node_t *n; // 遍历所有参数,直到遇到NULL终止符 while ((n = va_arg(lmnts, node_t *)) != NULL) { // 跳过和目标节点key相同的节点 if (apr_strnatcmp(n->key, p_target_node->key) == 0) { continue; } apr_array_header_t *tbl = NULL; switch(n->type) { case node_type_string: tbl = p_target_node->arr_strings; break; case node_type_number: tbl = p_target_node->arr_numbers; break; default: fprintf(stderr, "⚠️ Unknown node type for key: %s\n", n->key); continue; } if (!tbl) { continue; } printf("\t\t[%d] Added node: %s\n", rv, n->key); *(node_t**)apr_array_push(tbl) = n; rv++; } va_end(lmnts); return rv; } int main(int argc, const char *argv[]) { apr_status_t rv; apr_pool_t *mp; rv = apr_initialize(); if (rv != APR_SUCCESS) { fprintf(stderr, "Failed to initialize APR\n"); return -1; } apr_pool_create(&mp, NULL); node_t *ROOT_NODE = create_node(mp, "THE_ROOT", node_type_string); printf("\tROOT_NODE { key: '%s', type: %d, strings: %d, numbers: %d}\n", ROOT_NODE->key, ROOT_NODE->type, ROOT_NODE->arr_strings->nelts, ROOT_NODE->arr_numbers->nelts); node_t *NODE_A = create_node(mp, "A", node_type_string); printf("\tNODE_A { key: '%s', type: %d, strings: %d, numbers: %d}\n", NODE_A->key, NODE_A->type, NODE_A->arr_strings->nelts, NODE_A->arr_numbers->nelts); node_t *NODE_B = create_node(mp, "B", node_type_number); printf("\tNODE_B { key: '%s', type: %d, strings: %d, numbers: %d}\n", NODE_B->key, NODE_B->type, NODE_B->arr_strings->nelts, NODE_B->arr_numbers->nelts); node_t *NODE_C = create_node(mp, "C", node_type_string); printf("\tNODE_C { key: '%s', type: %d, strings: %d, numbers: %d}\n", NODE_C->key, NODE_C->type, NODE_C->arr_strings->nelts, NODE_C->arr_numbers->nelts); // 注意最后必须加NULL作为可变参数的终止标记! int added_count = add_node(mp, ROOT_NODE, NODE_A, NODE_B, NODE_C, NULL); printf("\n✅ Added %d nodes total\n", added_count); printf("\tROOT_NODE now has %d string nodes, %d number nodes\n", ROOT_NODE->arr_strings->nelts, ROOT_NODE->arr_numbers->nelts); apr_pool_destroy(mp); apr_terminate(); return 0; }
现在这个代码应该能正常运行,不会再出现段错误,而且能正确把NODE_A、NODE_C(字符串类型)加到ROOT的arr_strings,NODE_B(数字类型)加到arr_numbers里。
内容的提问来源于stack exchange,提问作者Kérdezősködő Indián

