【问题标题】:What kind of errors set "errno" to non-zero? Why does fopen() set "errno" while fputc() does not?什么样的错误将“errno”设置为非零?为什么 fopen() 设置“errno”而 fputc() 不设置?
【发布时间】:2013-05-12 13:22:04
【问题描述】:

什么样的库函数面临什么样的错误会影响errno并将其设置为非零值?在下面的程序中,我打算使用if(errno!=0) 作为条件来检查我使用的库函数是否正常运行,这就是我发现的(见下面的代码):

首先我用if(errno!=0) 来测试一个文件是否已经用fopen() 成功打开。如果我尝试打开一个不存在的文件,则 errno 设置为非零(在我的情况下为 2),并通过在每个阶段打印出 errno 的值来验证。但是,如果我打开一个现有文件,则 errno 的值将保持为零,因为 fopen() 会正确打开该文件。在这件事上,if(errno!=0) 是我已注释掉的if(pFile==NULL) 的完美替代品。

如果文件成功打开,errno 仍然是0,则控制移动到第一个else 块。这就是我对errno 的行为感到困惑的地方。在这里,由于我已在 r(read) 模式下打开文件并尝试使用fputc() 对其进行写入,因此我希望生成的写入错误将errno 设置为非零,就像无法成功打开文件时由fopen()设置。但即使在使用fputc() 写入失败后,errno 的值仍然为零。 (这可以通过在错误写入后打印errno 的值来验证)。

为什么会这样?为什么设置errno 的一个函数fopen() 面临I/O 错误,而其他函数fputc() 面临的写入错误不影响errno?如果是这样,我们如何可靠地使用errno 作为错误指示器? 我是否使用 errno 来测试 fopen() 是否成功运行,而不是不明智地使用“if(pFile==NULL)”?我将感谢您对此的分析回答。

#include <stdio.h>
#include <errno.h>

int main ()
{

  FILE * pFile;
  printf("%d\n",errno);

  pFile = fopen("D:\\decrypt.txt","r");
  printf("%d\n",errno); // Prints 0 if fopen() successful,else 2

  //if(pFile==NULL) perror("Error opening file"); 

   if (errno!=0) perror ("Error opening file");

  else
  {
    fputc ('x',pFile);
     printf("%d\n",errno); //errno shows 0 even after write error

     //if (ferror (pFile))
    if (errno!=0)  //Condition evaluates false even if faulty write
      {
      printf ("Error Writing to decrypt.txt\n"); 
        }
    fclose (pFile);

  }
  return 0; 
}

【问题讨论】:

  • POSIX says: "只有当函数的返回值表明 errno 的值有效时,才应该检查它。"
  • @Mat C 标准是怎么说的?..when it is indicated to be valid by a function's return value. 是什么意思?你能把它放在一个更详细的答案中吗?
  • @Rüppell'sVulture 查看链接以获得更多答案。并非每个库函数都设置 errno。 C标准没有说明哪些函数设置了errno,甚至设置errno也不一定是实际错误。
  • @Rüppell'sVulture:hmjd 已经引用了 C 标准。

标签: c fopen errno fputcsv


【解决方案1】:

文档主要告诉你哪个函数可以设置errno中的哪些值,但有一些规则你需要知道:

  1. 没有库函数将errno 设置为零。
  2. 仅当函数指示发生错误时测试 errno 才有效(并且函数记录为设置 errno)。

第一点的意思是,如果你想知道,例如,你是否从strtol()得到一个错误,你必须在调用它之前将errno设置为0。

第二点很关键;例如,在 Solaris 上,当通道不是终端时,经过多次 I/O 操作后,errno 的设置将是 ENOTTY(不是终端)。没有错误;什么都没有失败;但是仅基于errno(而不是基于 I/O 操作报告的状态)的后续操作会导致您认为一切都失败了。

因此,在您的代码中,fopen() 调用可能会将errno 保留为非零值,即使它成功创建了文件流。你必须使用:

const char filename[] = "D:\\crypt.txt";
if ((pFile = fopen(filename, "r")) == 0)
{
    fprintf(stderr, "Failed to open %s for reading (%d: %s)\n",
            filename, errno, strerror(errno));
    ...return or exit...
}

注意:如果您需要调用可以更改errno 的函数,请尽早捕获该值:

    int errnum = errno;
    fprintf(stderr, "Failed to open %s for reading (%d: %s)\n",
            filename, errnum, strerror(errnum));

永远不要自己声明errno;总是使用#include &lt;errno.h&gt; 来做。

我不清楚为什么您的代码在调用 fputc() 时没有出错。在我的 Mac OS X 10.8.3 系统上,等效代码失败,errno 设置为 9 (EBADF) 'Bad file descriptor'。


这在哪里记录?它在 C 标准中,并由 POSIX 标准加强。

ISO/IEC 9899:2011 §7.5 错误&lt;errno.h&gt;

¶3 程序启动时初始线程中errno 的值为零(其他线程中errno 的初始值为不确定值),但绝不会被任何库函数设置为零。202)errno 的值可以通过库函数调用设置为非零,无论是否存在错误,前提是 errno 的使用未记录在本国际函数的描述中标准。

202) 因此,使用errno 进行错误检查的程序应在调用库函数之前将其设置为零,然后在调用后续库函数之前对其进行检查。当然,库函数可以在入口处保存errno的值,然后将其设置为零,只要在返回之前errno的值仍然为零,则恢复原始值。

以前版本的 C 标准中的措辞没有提及线程,但在其他方面类似。

请注意,C 标准中对fopen() 的描述没有提及errno。因此,C 标准允许设置errno。相比之下,mbsrtowcs() 函数被记录为将errno 设置为 EILSEQ;它可能无法将其设置为其他值,因为 C 标准规定它不应该(尽管如果在某些条件下有更好的错误,没有什么可以阻止实现这样做)。

POSIX 2008

errno 的 POSIX 页面说:

许多函数在errno 中提供了一个错误号,其类型为int,并在&lt;errno.h&gt; 中定义。 errno 的值应仅在调用明确声明为其设置的函数之后定义,直到它被下一个函数调用更改或应用程序为其分配值。 errno 的值只有在函数的返回值表明它是有效的时候才应该被检查。应用程序应通过包含&lt;errno.h&gt; 来获得errno 的定义。本卷 POSIX.1-2008 中的任何函数都不应将 errno 设置为 0。在成功调用函数后未指定 errno 的设置,除非该函数的描述指定不应修改 errno

未指定errno 是宏还是使用外部链接声明的标识符。如果为了访问实际对象而禁止宏定义,或者程序定义了名称为 errno 的标识符,则行为未定义。

存储在 errno 中的符号值记录在所有相关页面的 ERRORS 部分中。

以前版本的措辞类似。

【讨论】:

  • 关于问题中 fputc 的具体故障,Rüppell 在 Windows 上。没有记录来自 Windows CRT 的 fputc 为此类问题设置 errno。
  • @JonathanLeffler 你能花点时间告诉我hacking the file names 怎么样?我对 linux 世界很陌生。你的意思是文件路径的格式与 windows 不同吗?
  • @JonathanLeffler 我是个可怜的家伙,我第一次将 Windows CRT 读作“Windows 阴极射线管”。这是我在闻到老鼠味并用谷歌搜索找到它是 Windows C 运行时之前所知道的唯一 CRT毕竟是图书馆。
  • Unix 上的路径名使用斜杠而不是反斜杠作为分隔符,绝对名称以/ 开头。 Windows 上没有驱动器。因此/bin/sh 是绝对路径名,abc/def 是相对路径名。在 Windows 上,您可以使用斜杠代替反斜杠,但驱动器号仍然是一个主要区别。其他操作系统使用不同的方案:DEC VMS file system 是一个值得注意的例子,但 PrimOS 也使用 &gt; 而不是斜杠或反斜杠来分隔路径名组件。
【解决方案2】:

我使用 errno 来测试 fopen() 是否成功,而不是使用“if(pFile==NULL)”是不明智的吗?

来自 C99 标准的 7.5 Errors/3

errno 的值在程序启动时为零,但绝不会被任何库函数设置为零。159) 无论是否存在错误,库函数调用都可以将 errno 的值设置为非零,前提是本国际标准的功能描述中没有记录 errno 的使用。

因此检查errno 来确定操作的成功或失败是不明智的,因为允许一个函数悲观地设置errno 的值来指示失败,即使没有失败。仅在函数失败时查询errno(例如,如果fopen() 返回NULLfputc() 返回EOF)。

【讨论】:

【解决方案3】:

刚刚查看了手册页以确认有效的错误编号都是非零的; errno 绝不会被任何系统调用或库函数设置为零。

检查the Linux errno.h man page: 相信你应该先看返回值再看errno

【讨论】:

    猜你喜欢
    • 2015-06-05
    • 1970-01-01
    • 1970-01-01
    • 2018-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-29
    • 2010-11-08
    相关资源
    最近更新 更多