C 在使用varargs的printf包装中没有返回错误

C 在使用varargs的printf包装中没有返回错误,c,variadic-functions,stdio,C,Variadic Functions,Stdio,我最近重新开始使用C,我决定编写一个库作为stdio.h的包装器。我们的目标是尽可能地进行所有错误检查,这样用户就不必在调用stdio函数时自己进行检查。这部分是为了学习,部分是为了实际使用,因为我经常使用stdio 当我在main中编写以下内容时,gcc在编译时给出了一个错误,因为应该有一个整数作为另一个参数,但没有传递任何参数 printf("Your integer: %d\n"); 如果有用,以下是我的编译器标志: -std=c11 -pedantic-errors -Wall -We

我最近重新开始使用C,我决定编写一个库作为stdio.h的包装器。我们的目标是尽可能地进行所有错误检查,这样用户就不必在调用stdio函数时自己进行检查。这部分是为了学习,部分是为了实际使用,因为我经常使用stdio

当我在main中编写以下内容时,gcc在编译时给出了一个错误,因为应该有一个整数作为另一个参数,但没有传递任何参数

printf("Your integer: %d\n");
如果有用,以下是我的编译器标志:

-std=c11 -pedantic-errors -Wall -Wextra -Werror
这是我当前包装器函数的一部分。当传递有效/正确的参数时,它可以完美地工作并检查许多错误:

uintmax_t gsh_printf(const char *format, ...)
{
    va_list arg;
    int cnt;
    va_start(arg, format);
    cnt = vprintf(format, arg);
    va_end(arg);
    // Analyze cnt and check for stream errors here
    return (uintmax_t)cnt;
}
gsh_printf("Your integer: %d\n", 0, 1);
但问题是,如果我打电话:

gsh_printf("Your integer: %d\n");
它不会给出错误,甚至会运行!通常的输出类似于:

Your integer: 1799713
或者其他数字,这意味着它正在访问未分配给它的内存,但它也不会给出分段错误

那么,为什么它不给出任何类型的错误呢?我如何编写自己的代码,从而在检查类型、参数数量等之后出现编译时错误,或者至少出现运行时错误


当然,非常感谢您的帮助,如果您需要更多信息,请告诉我。谢谢大家!

在C语言中,无法确定使用va_list时传递的参数数量是否为所需的参数数量。在C调用约定中,参数从最右边的参数开始推送到堆栈上。printf的工作方式是解析格式字符串,并在需要时从堆栈中弹出一个值。因此,您在呼叫时会得到随机数

gsh\u打印您的整数:%d\n

您需要提前知道提供了多少个参数,这些参数不能使用va_列表来完成

您可以通过使用某种容器类来保存所有参数,并使用容器中的元素数来检查是否有足够的元素来解决这个问题


还要注意,“args”只是指向参数列表开头的指针。因此,当您将其传递给vprintf时,vprintf只打印指针的值。

在C中,无法确定使用va_list时传递的参数数是否为所需的参数量。在C调用约定中,参数从最右边的参数开始推送到堆栈上。printf的工作方式是解析格式字符串,并在需要时从堆栈中弹出一个值。因此,您在呼叫时会得到随机数

gsh\u打印您的整数:%d\n

您需要提前知道提供了多少个参数,这些参数不能使用va_列表来完成

您可以通过使用某种容器类来保存所有参数,并使用容器中的元素数来检查是否有足够的元素来解决这个问题

还要注意,“args”只是指向参数列表开头的指针。因此,当您将其传递给vprintf时,vprintf只打印指针的值。

对于fprintf和fscanf函数族,如果缺少转换规范对应的参数,函数调用将调用未定义的行为

使用gcc使用格式原型、字符串索引,首先检查函数属性以请求诊断:

extern uintmax_t gsh_printf(const char *format, ...)
    __attribute__ ((format (printf, 1, 2)));

uintmax_t gsh_printf(const char *format, ...)
{
    va_list arg;
    int cnt;
    va_start(arg, format);
    cnt = vprintf(format, arg);
    va_end(arg);
    // Analyze cnt and check for stream errors here
    return (uintmax_t)cnt;
}
有关原型、字符串索引和第一个要检查的项目的说明,请参阅文档:

例如,在上面的示例中,由于-Wformat而使用-Wall,使用以下语句:

gsh_printf("Your integer: %d\n");
您将收到以下警告:

警告:格式“%d”需要匹配的“int”参数[-Wformat]

对于-Wall,由于-Wformat额外参数,您还将收到额外参数的警告:

uintmax_t gsh_printf(const char *format, ...)
{
    va_list arg;
    int cnt;
    va_start(arg, format);
    cnt = vprintf(format, arg);
    va_end(arg);
    // Analyze cnt and check for stream errors here
    return (uintmax_t)cnt;
}
gsh_printf("Your integer: %d\n", 0, 1);
给予

警告:格式[-Wformat extra args]的参数太多

对于fprintf和fscanf函数族,如果缺少转换规范对应的参数,函数调用将调用未定义的行为

使用gcc使用格式原型、字符串索引,首先检查函数属性以请求诊断:

extern uintmax_t gsh_printf(const char *format, ...)
    __attribute__ ((format (printf, 1, 2)));

uintmax_t gsh_printf(const char *format, ...)
{
    va_list arg;
    int cnt;
    va_start(arg, format);
    cnt = vprintf(format, arg);
    va_end(arg);
    // Analyze cnt and check for stream errors here
    return (uintmax_t)cnt;
}
有关原型、字符串索引和第一个要检查的项目的说明,请参阅文档:

例如,在上面的示例中,由于-Wformat而使用-Wall,使用以下语句:

gsh_printf("Your integer: %d\n");
您将收到以下警告:

警告:格式“%d”需要匹配的“int”参数[-Wformat]

对于-Wall,由于-Wformat额外参数,您还将收到额外参数的警告:

uintmax_t gsh_printf(const char *format, ...)
{
    va_list arg;
    int cnt;
    va_start(arg, format);
    cnt = vprintf(format, arg);
    va_end(arg);
    // Analyze cnt and check for stream errors here
    return (uintmax_t)cnt;
}
gsh_printf("Your integer: %d\n", 0, 1);
给予

警告:格式[-Wformat extra args]的参数太多


看看定义printf的头文件,它必须有某种注释,以便编译器知道如何将其视为我之前检查过的格式string,但stdio.h中没有任何提示,只是一个合理的正常函数声明。我找不到stdio.c文件,所以我假设它可能是用另一种语言编写的,或者是预编译的,或者是其他什么东西。它是语法上的
gal无法在不添加额外参数的情况下传递printf a%d,因为printf使用C的变量参数。编译器抱怨的原因是它被编程为这样做。它尽可能地注意到这可能是一个永远不会出现的语义问题,因此它抛出了一个警告/错误。正在显示的数据是垃圾。它不抛出分段错误的原因可能是因为它将先前的堆栈值解释为缺少的int参数。除非您检查页面或其他操作系统错误,否则您可能不会看到错误。@MrHappyAsthma您是说标准不要求printf检查正确数量的参数吗?如果是这样的话,我可以忍受用户被迫检查自己的printfs,当然最好是检查它们。是的。从技术上讲,它是函数的未定义行为。但大多数编译器能够并将检测到这一点。编辑:Printf使用格式字符串来检测参数的数量,而不是将该数量作为附加参数或类似的任何内容传递。函数假定格式字符串是正确的,这取决于用户或一个好的编译器来确保它的准确性。查看定义printf的头,它必须有某种注释,以便编译器知道将其视为我之前检查过的格式字符串,但stdio.h中没有任何提示,只是一个合理正常的函数声明。我找不到stdio.c文件,所以我假设它可能是用另一种语言编写的,或者是预编译的,或者其他什么。从语法上讲,传递printf a%d而不添加其他参数是合法的,因为printf使用c的变量参数。编译器抱怨的原因是它被编程为这样做。它尽可能地注意到这可能是一个永远不会出现的语义问题,因此它抛出了一个警告/错误。正在显示的数据是垃圾。它不抛出分段错误的原因可能是因为它将先前的堆栈值解释为缺少的int参数。除非您检查页面或其他操作系统错误,否则您可能不会看到错误。@MrHappyAsthma您是说标准不要求printf检查正确数量的参数吗?如果是这样的话,我可以忍受用户被迫检查自己的printfs,当然最好是检查它们。是的。从技术上讲,它是函数的未定义行为。但大多数编译器能够并将检测到这一点。编辑:Printf使用格式字符串来检测参数的数量,而不是将该数量作为附加参数或类似的任何内容传递。该函数假定格式字符串是正确的,这取决于用户或一个好的编译器,以确保这是准确的。有趣的想法。我的部分目标是保持函数参数与标准参数相同,以实现向后兼容性,但这值得考虑。这是一个有趣的想法。我的部分目标是使函数参数与标准参数保持一致,以实现向后兼容性,但这是值得考虑的。我已经尝试过了,而且效果非常好!我希望它是标准的,但它是一个很好的,干净的解决方案。非常感谢。我试过了,效果非常好!我希望它是标准的,但它是一个很好的,干净的解决方案。非常感谢。