【问题标题】:What is the difference between fprintf and vfprintf in C++? [closed]C++ 中的 fprintf 和 vfprintf 有什么区别? [关闭]
【发布时间】:2013-01-16 12:28:06
【问题描述】:

我无法找到以下问题的答案,并且遇到了一些与功能相关的问题。

我的主要编程是用 C# 完成的,在学习期间从未真正学习过 C++,但在我目前的工作中,我还必须做一些 C++ 编程。

大部分 C++ 编程都是由一位前雇员完成的,他制作了一个日志功能。

这个函数偶尔会导致错误(访问冲突) - 这不会向用户显示,但我在通过调试器运行代码时会看到它。

当错误发生时,它指向这行代码:

vfprintf( LogFile, fmt, va );

然后我仔细查看了之前和之后的代码,并将上面的代码放入上下文中,代码是:

void FileLog( char *fmt, ... )
{
  va_list       va;
  struct  time  t;
  struct  date  d;
  long          clk;
  static int    ReEntrant = 0;

  if( FileLogEnabled == false )
    return;

  ReEntrant++;
  if( ReEntrant > 1 )
    return;

  if( LogFile == NULL )
    LogFile = fopen( LogFileName, "a+" );
  if( LogFile != NULL )
  {
    gettime( &t );
    getdate( &d );
    fprintf( LogFile, "\n%d-%02d-%02d %2d:%02d:%02d.%02d0> ", d.da_year, d.da_mon, d.da_day, t.ti_hour, t.ti_min, t.ti_sec, t.ti_hund );

    va_start( va, fmt );
    vfprintf( LogFile, fmt, va );
    va_end( va );

    fflush( LogFile );
    ...
  }
  ReEntrant = 0;
}

实际上我不明白为什么需要它(如果需要?)调用 fprintf 然后调用 vfprintf?我认为第一个 fprintf 调用会将格式化的字符串写入流(文件),这就足够了?

非常感谢您提供一些解释或一些信息:)

编辑:在 nos 发表评论后 - 我追踪了对今天经常导致此错误的函数的特定调用。

FileLog( "TimerRestore[%d], Name=%s", Package.CurGame->Timers[ Index ].Name.c_str() );

我确实认为这可能会造成一些麻烦,因为“TimerRestore[%d], Name=%s”后面应该跟着一个小数和字符串arguemtn,但是只给出了一个字符串参数。我需要做一些测试,但我确定写这段代码的作者是想写的:

FileLog( "TimerRestore[%d], Name=%s", Index, Package.CurGame->Timers[ Index ].Name.c_str() );

但是我仍然不明白为什么函数调用似乎并不总是导致错误。或者可能是 FileLog 函数中的“ReEntrant”变量在它没有失败时阻塞它的原因?

非常感谢您提供的所有反馈和信息。

【问题讨论】:

  • 请不要将 C++ 和 C 放在一个人为的术语“C/C++”中。它们是具有不同习语的不同语言。
  • 这个日志记录函数是否被多个线程调用? gettimegetdate 函数中是否涉及任何静态?
  • 如果您查看代码,第一个 fprintf() 调用仅将日期/时间写入日志文件。 vfprintf() 调用打印实际的日志消息。如果调用者为日志消息提供了他自己的格式字符串,则这些不能组合。但是,实际的错误不在调试器停止的那一行,这是因为有人使用无效参数调用此函数,您必须对其进行跟踪。
  • 实际上不,看起来您对导致问题的原因是正确的。我仍然不完全明白为什么它不会每次都发生。我“经常”(例如 20-30% 的时间)在执行此行时发生: FileLog("TimerRestore[%d], Name=%s", Package.CurGame->Timers[ Index ].Name.c_str( ) );.事实上,我认为第一个字符串应该有 2 个参数,而它只有 1 个。但是为什么它并不总是失败呢?
  • @Knirkegaard 该行有一个明显的错误,会导致不可预知的行为。 TimerRestore[%d], Name=%s" 包含 2 个格式说明符“%d”和“%s”,但您只提供一个参数 Package.CurGame->Timers[ Index ].Name.c_str()

标签: c++ c access-violation printf


【解决方案1】:

vprintf()(和朋友)允许使用va_list 作为参数,这在您的函数具有可变数量的参数时很有用:

void log(FILE *file, const char* format, ... )
{
  va_list args;
  va_start (args, format);
  fprintf(file, "%s: ", getTimestamp());
  vfprintf (file, format, args);
  va_end (args);
}

在您的应用程序中,您可以使用可变数量的参数调用此函数:

log(file, "i=%d\n", i);           // 3 arguments
log(file, "x=%d, y=%d\n", x, y);  // 4 arguments

我不知道您的函数为什么会导致错误。您的代码 sn-p 没有提供足够的详细信息。提供的函数参数类型的数量可能是原因。

【讨论】:

  • 是的,这也是在应用程序中调用日志函数的一种方式:void FileLog( char *fmt, ... )
【解决方案2】:

首先,在 C++ 中使用 fprintf() 和(尤其是)vfprintf()邪恶的。

直截了当:fprintf() 是一个可变参数函数,它接受任意数量的参数。在内部,可变参数函数是通过使用va_listva_start()va_end()“解包”可变参数来实现的。

vfprintf() 用于当你想从你自己的可变参数函数中访问fprintf() 的功能时你已经解压了你自己的可变参数参数(也就是说,你可以访问一个@ 987654329@ 实例)。 vfprintf() 不是可变参数;它接受一个 va_list 存储参数。

您尚未发布调用fprintf()vfprintf() 的函数的声明,但我们可以假设它是可变参数。它首先使用fprintf() 将一些数据打印到LogFile,然后使用vfprintf() 在那里打印它自己的可变参数。

【讨论】:

  • 你能提供一个链接或其他证据证明它是邪恶的吗?
  • @paddy 我手头没有链接,但我会解释一下:fprintf() 类型不安全。如果参数和格式说明符不匹配,这是一个运行时错误,而对于 iostreams,它将是一个编译时错误。使用vfprintf() 意味着编写一个(C 风格的)可变参数函数,这又是极其不安全的类型。将非平凡的类传递给此类函数是“有条件地支持实现定义的语义”(引用标准)。
  • 当然,但不是类型安全并不会使它变得邪恶......这只是意味着你必须小心。如果使用得当,printf 系列功能非常强大,是手头上的绝佳工具。您的另一点更合理,但我认为您永远不需要将非平凡数据传递到可变参数函数的可变参数部分。当然不会调用vfprintf
  • @paddy 够公平的。也许我只是喜欢夸张 :-) 我只是补充一点,你不需要需要传递非平凡数据并不意味着你不会传递这样(例如意外),如果你这样做了,运行时错误就会潜伏。
  • 是的,当然,如果你倾向于依赖从一个类到某个值的隐式强制转换(或者如果你有一天改变了某个成员函数的返回类型),那么这会很快变得很糟糕。我也完全支持类型安全,但我很高兴在迂腐无法获得回报的情况下保持灵活性。
【解决方案3】:

vfprintf 允许您拥有一个变量参数列表,这意味着您可以在运行时动态创建参数列表。

【讨论】:

    【解决方案4】:

    这在日志记录中很常见。您想制作一个 printf 样式的日志消息,例如:

    Log("The value of x is now %d", x);
    

    但这需要可变参数。所以你需要vfprintf。也使用fprintf 的原因是因为它想要写入日期/时间戳,而您不能将额外的内容添加到传递给vfprintf 的现有格式中。

    另一种方法是使用字符串版本vsprintf,并制作一个大字符串,然后将其写入文件。但这更容易出错(比如缓冲区溢出)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-05
      • 1970-01-01
      • 2021-03-24
      • 1970-01-01
      • 2023-03-03
      • 1970-01-01
      • 2011-06-26
      • 1970-01-01
      相关资源
      最近更新 更多