【问题标题】:Why can't errno's value be printed?为什么不能打印errno的值?
【发布时间】:2012-07-14 19:03:00
【问题描述】:

我正在查看 SO“低质量”帖子中的以下代码以确保示例有效,我的问题是为什么我不能打印 errno 的值?

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

int main(){
    FILE *fp;
    errno = 0;
    fp=fopen("Not_exist.txt","r");
    if(fp == NULL && errno == ENOENT)
        perror("file not exist");
    return 0;
}

当我尝试打印该值时会发生以下情况:

(gdb) p errno
Cannot find thread-local variables on this target
(gdb)

我可以很好地打印 fp 的值。如您所料,它的值为0x00

我查看了/usr/include/errno.h 和许多其他包含在errno.h 中的包含文件,但我无法弄清楚errno 是如何定义的。任何指示或帮助将不胜感激。我只是对此感到好奇;什么都没有。

谢谢。

【问题讨论】:

  • 下次尝试使用ferror(my-file-pointer)
  • 你能从你的程序中打印出errno 的值吗? printf("errno = %d\n", errno);
  • 在检查核心转储时是否也可以打印errno?那时你不能运行任何功能。

标签: c gdb errno


【解决方案1】:

errno 变量有点奇怪。因为现在大多数运行时库都支持线程,所以不能只有 一个 errno 变量。如果有,那么两个线程可以同时做一些事情,都设置了errno 值,然后就会产生很大的混乱。

运行时库会使用各种技巧来避免这个问题。例如,可能会执行以下操作:

#define errno __get_errno()

其中对errno 的引用实际上调用了内部__get_errno() 函数,该函数返回当前线程的正确错误编号值。这种方法的缺点是它会阻止分配给 errno,例如 errno = 0;(某些代码可能会这样做)。运行时库通常会选择更复杂的方法。

一些运行时库(我想就像您正在使用的那个)可以声明一种特殊类型的“线程局部变量”,它可以在每个线程上具有不同的值。听起来您系统上的调试器无法显示这种变量。

【讨论】:

  • 我见过的方法是#define errno *__get_errno()__get_errno() 返回一个指针,宏取消引用它。这样,它是可赋值的并且仍然封装在一个函数中。
  • 是的,这是在没有特定编译器支持的情况下进行线程本地存储的一种方式。
【解决方案2】:

在我的 Ubuntu 安装中,bits/errno.h 中有以下部分:

/* Function to get address of global `errno' variable.  */
extern int *__errno_location (void) __THROW __attribute__ ((__const__));

#  if !defined _LIBC || defined _LIBC_REENTRANT
/* When using threads, errno is a per-thread value.  */
#   define errno (*__errno_location ())
#  endif

也就是说,errno 不一定是变量。由于各种原因,您可能希望有一个函数为您返回错误值,而不是简单的extern int1这就是为什么您不能使用 GDB 打印它的值。

1当然,如您所见,函数调用应该返回指向实际变量的指针,而errno 宏会取消引用它。

【讨论】:

  • errno 必然是一个左值,所以你可以赋值给它。
  • @KeithThompson,我没有说它不是左值。我说它不一定是一个变量。 gdb,或者至少是那个特定版本,将能够读取由名称标记的内存位置(例如变量i),但可能不能(在这种情况下似乎是这样)执行一个函数来返回它的值。
  • 我的意思是errno 不能是(定义为的宏)返回int 的函数;你最后一段中的第二句话可能暗示它可以。当然,它可以是一个扩展为一个表达式的宏,该表达式包含对返回 int* 的函数的调用。
  • @KeithThompson,我添加了一条评论来澄清。
【解决方案3】:

正如其他人所说,errno 不是 gdb 可以打印的变量。但是gdb可以 评估函数,__errno_location() 返回一个指向 `errno' 的指针。那么我们唯一需要做的就是调用函数并取消引用结果:

(gdb) p *__errno_location()

就是这样。

【讨论】:

    【解决方案4】:

    errno 实际上是 C 标准要求的扩展为可修改左值的宏。在最简单的情况下,它可以扩展为已声明变量的名称,但对于需要不同线程的不同 errno 对象的实现,它通常定义如下:

    #define errno (*__errno_location ())
    

    gdb 通常能够评估函数调用;例如,在我的系统上:

    (gdb) p __errno_location()
    $1 = -134383968
    (gdb) p errno
    Cannot find thread-local variables on this target
    

    第一个打印的值恰好是__errno_location() 返回的指针值的低 32 位。我不太了解 gdb 来解释这种行为,但它确实证明了它可以执行函数调用。

    作为一种解决方法,您可以修改源代码,以便将errno 的地址或其值保存在 gdb 可以显示的变量中:

    (gdb) l
    1       #include <errno.h>
    2       #include <stdio.h>
    3       int main(void) {
    4           errno = 42; /* arbitrary value */
    5           const int *errno_ptr = &errno;
    6           int errno_value = errno;
    7           printf("%d %d %d\n", errno, errno_value, *errno_ptr);
    8       }
    (gdb) b 8
    Breakpoint 1 at 0x4005b6: file c.c, line 8.
    (gdb) r
    Starting program: /home/kst/c 
    42 42 42
    
    Breakpoint 1, main () at c.c:8
    8       }
    (gdb) p errno
    Cannot find thread-local variables on this target
    (gdb) p errno_value
    $1 = 42
    (gdb) p *errno_ptr
    $2 = 42
    

    *errno_ptr 方法的优点是您只需分配一次 -- 除非您正在调试多线程程序。在这种情况下,&amp;errno 的值可能会因您评估它的线程而异。

    这可能是gdb 中的一个错误,或者至少是一个缺失的功能。

    更新 Kevin Cox 的评论提出了一种解决方法:

    print *((int*(*)())__errno_location)()
    

    对于 gcc 6.2 和 gdb 7.11,print errno 确实有效:

    (gdb) l
    1       #include <errno.h>
    2       int main(void) {
    3           errno = 42;
    4           return 0;
    5       }
    (gdb) b 4
    Breakpoint 1 at 0x6bf: file c.c, line 4.
    (gdb) r
    Starting program: /home/kst/c 
    
    Breakpoint 1, main () at c.c:4
    4           return 0;
    (gdb) p errno
    $1 = 42
    (gdb) 
    

    【讨论】:

    • 它被截断的原因是因为没有提供函数签名的调试信息,所以 gdb 假定它是 int()() 当它实际返回一个 int 时。要解决此问题,您可以将符号转换为正确的函数类型,然后再调用它print *((int*(*)())__errno_location)()
    • 请注意,如果您使用调试符号进行编译,打印errno 将始终有效,问题是如果您没有调试符号,编译器不知道 errno 是什么(因为它是一个宏)。
    • @KevinCox:嗯。我假设如果你要使用gdb,无论如何你都需要使用调试符号(gcc -g ...)进行编译。
    • 您不需要这样做,有时您会陷入符号不可用的不幸情况。相信我,当没有可用的符号时能够读取 errno 是 huuuuge 的好处。
    【解决方案5】:
    _CRTIMP int* __cdecl __MINGW_NOTHROW _errno(void);
    #define errno       (*_errno())
    

    这样,您可以只传递首选验证的地址,该地址将包含从调用函数返回的实际错误值。

    例如您可以定义函数 _errno() 如下

    unsigned int errorValue;
    
    int* _errno()
    {
        return (&errorValue);
    }
    

    现在用法:

    void MyFunc()
    {
      if(some condition failure)
          errno = 10; //Or any error value as per your design
      else
      {
         //Actual operation
      }
    }
    

    MyFunc() 执行后,errorValue 将包含错误。

    【讨论】:

    • 这如何回答这个问题?
    猜你喜欢
    • 1970-01-01
    • 2016-05-06
    • 2012-02-25
    • 2014-04-22
    • 2017-11-24
    • 2013-09-23
    • 2011-03-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多