所以这里的上下文是我正在开发自己的vfscanf实现。我目前有以下两个函数签名:
int k_fscanf_l(uint32 HA, FILE *stream, int max_length, char *format_tok, int *bytes_moved, void *va_list_item)
int k_vfscanf(uint32 HA, FILE *stream, const char *format, va_list args)
K表示内核,V表示可变长度,L只是我的字符串长度会计变体。
k_fscanf_l
是一个辅助功能,可以处理一堆繁重的工作k_vfscanf
.就每个函数的具体作用而言:
k_vfscanf
采用格式字符串并将其分解为单数标记并单独传递给k_fscanf_l
,或者在字符的情况下传递给单独的k_fread
函数(不要...问)。在里面k_fscanf_l
我打电话给sscanf(lstrPtr, mod_tok, va_list_item, &offset)
为我做大部分繁重的工作,除此之外,我自己处理文件的东西。
我可以说的还有很多话来过度描述这一点,但我相信更容易找到问题的症结,这与每个函数中的最后一个参数有关。我的程序目前正在断言从索引到va_list args
,所以我想问:
问题:
- 像这样索引到
va_list args
中是严格不正确的:(void *)args[va_list_index]
并将其传递给k_fscanf_l
?
我 - 应该使用预定义的宏而不是 void 指针来实现我想要的吗?
- 甚至有可能实现我想要的吗?也就是说,我可以将
va_list args
划分为多个void *va_list_item
指针,由帮助程序函数内部的sscanf
填充吗?我必须更改函数签名吗? - 如果我调用
va_arg
宏,我是否指定了类型,或者是否有(void *)式解决方案? - 如果上面没有回答,在这种情况下你会推荐什么?
我目前对格式字符串进行标记化并在同一循环中调用帮助程序函数,但我可以将它们分成 2 次传递,以严格知道在使用va_list之前我将进行多少次迭代,我只是不知道我是否可以传递单个项目进行填充即使我这样做。
编辑 2:更新代码段以使其更具包容性
else if (tok_len > 1)
{
// Character(s) - Deterministic length and compatibility with byte characters
if (ft_low == 'c')
{
int num_chars;
ret_val = sscanf(formatTok, "%d", &num_chars);
if (ret_val < 1)
num_chars = 1;
k_print_message(HA, NOTE, 0, "vfscanf", "tGot %d characters for specifiern", num_chars);
ret_val = k_fread(HA, va_arg(args, void *), num_chars, 1, stream);
}
// %N case - Does not need to actually call scanning function
else if (ft_low == 'n')
{
int *va_list_count = va_arg(args, int *);
*va_list_count = byte_offset;
}
// Strings - Hard to predict strings and scansets, support max length available for buffer
else if (ft_low == 's' || ft_low == ']')
ret_val = k_fscanf_l(HA, stream, MAX_LSTR_IN, formatTok, &temp_offset, va_arg(args, char *));
// Non-Floating Numbers - Common bases shouldn't reach 16 digits, ever
else if (ft_low == 'i' || ft_low == 'd' || ft_low == 'u' || ft_low == 'o' || ft_low == 'x' || ft_low == 'p')
ret_val = k_fscanf_l(HA, stream, 64, formatTok, &temp_offset, va_arg(args, int *));
// Floating Point Numbers - resolutions should never reach 64 digits, ever
else if (ft_low == 'f' || ft_low == 'e' || ft_low == 'g' || ft_low == 'a')
ret_val = k_fscanf_l(HA, stream, 64, formatTok, &temp_offset, va_arg(args, float *));
// Updates value
byte_offset += temp_offset;
if (ret_val > 0)
stored_items++;
else if (ret_val < 0)
break;
}
k_print_message(HA, NOTE, 0, "vfscanf", "tGot ft_low: %cn", ft_low);
k_print_message(HA, NOTE, 0, "vfscanf", "tGot ret_val: %dn", ret_val);
// Resets flag
null_flag = 0;
// Deallocates memory
kernel_free(HA, formatTok);
}
// Closes va_list from iteration
va_end(args);
- 像这样索引到
va_list args
是严格不正确的:(void *)args[va_list_index]
并将其传递给k_fscanf_l
?
这样做严格超出了 C 语言规范定义的语义范围。 因此,就C本身而言,这样做的行为是不确定的。 如果您的 C 实现碰巧记录了此类用法的语义,并且您不关心可移植性到其他 C 实现,那么这可能不是您的问题。 否则,这种做法充其量是不明智的。
我
- 应该使用预定义的宏而不是空指针来实现我想要的吗?
va_start
、va_arg
、va_end
和va_copy
宏是C语言规范记录的处理va_list
的唯一方法。 如果您的实现为此目的记录了其他功能,那么考虑使用它们是合理的(从而产生可移植性成本)。 如果不是,那么尝试使用宏以外的任何东西是不明智的。
- 甚至有可能实现我想要的吗?也就是说,我可以将
va_list args
划分为多个void *va_list_item
指针吗 由sscanf
在辅助函数中填充?我必须更改 函数签名?
与可变参数处理的细节无关,如果您的函数要支持 POSIX 样式的%n$
输入指令,则无法执行此类拆分,因为这些指令的显示顺序与相应指针的出现顺序脱节。 但这可能不是你关心的问题。
我还认为,尝试使用sscanf
来支持从文件扫描数据会带来一些与参数解释完全分开的重大挑战。 但我想你已经整理好了那部分。
就从va_list
获取目标指针而言,您确实具有以下优势:对于对此特定函数的有效调用,所有变量参数都必须是对象指针。 如果您可以依赖所有指向对象的指针类型来具有相同的表示形式,那么您可能可以通过一系列评估(例如......
void *va_list_item = va_arg(args, void *);
这可能足以满足您的目的。但是,当实际参数既不是指向void
的指针也不是指向字符类型的指针时,C 明确没有定义该方法的行为,这充其量是一种不舒服的情况。
如果要严格遵循 C,则必须至少解析格式字符串以确定每个参数的正确(指针)类型,并使用该类型从va_list
中提取参数。 这样做后,您可以安全地将结果转换为类型void *
以传递给您的帮助程序函数,并且此类转换甚至是自动的。
- 如果我调用
va_arg
宏,我是否指定了类型,或者是否有(void *)式解决方案?
见上文。
- 如果上面没有回答,在这种情况下你会推荐什么?
我是严格一致性的粉丝,所以一般来说,我建议使用它们的正确类型提取变量参数。 这提供了最不愉快的意外的可能性,尽管这确实意味着您将不得不重复sscanf
已经必须完成的一些工作。
另一方面,如果项目有某种通用开发策略,可以通过将对象指针类型视为可互换(而不仅仅是可互换)来打破严格的别名,那么通过假装变量参数都是类型void *
来依赖该策略几乎没有额外的危害。