Подтвердить что ты не робот

Почему нельзя печатать значение errno?

Я смотрю следующий код в 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. Любые указатели или помощь будут оценены. Мне просто интересно это; ничего не сломано.

Спасибо.

4b9b3361

Ответ 1

В моей установке 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 int. 1 Вот почему вы не можете распечатать свое значение с помощью GDB.

1, конечно, поскольку вы можете видеть, что вызов функции должен возвращать указатель на действительную переменную, а макрос errno мог бы разыменовать его.

Ответ 2

Переменная errno - это нечетная утка. Поскольку большинство библиотек времени выполнения в эти дни поддерживают потоки, не может быть только одна переменная errno. Если бы это было так, то два потока могли бы делать то же самое, что и установили значение errno, и возникла большая путаница.

Библиотеки времени выполнения выполняют различные трюки, чтобы избежать этой проблемы. Например, можно сделать что-то вроде:

#define errno __get_errno()

где ссылки на errno фактически вызывают внутреннюю функцию __get_errno(), которая возвращает правильное значение номера ошибки для текущего потока. Недостатком этого метода является предотвращение назначения errno, например errno = 0; (что может сделать какой-то код). Библиотеки времени выполнения обычно выбирают более сложный подход.

Некоторые библиотеки времени исполнения (например, тот, который вы используете, я полагаю) могут объявлять специальный тип "thread-local variable", который может иметь различное значение для каждого потока. Похоже, ваш отладчик в вашей системе не может отображать такую ​​переменную.

Ответ 3

errno на самом деле требуется, чтобы стандарт C был макросом, который расширяется до изменяемого значения lvalue. В простейшем случае он может расширяться до имени объявленной переменной, но для реализаций, которым требуются разные объекты errno для разных потоков, обычно он описывает что-то вроде этого:

#define errno (*__errno_location ())

gdb обычно может оценивать вызовы функций; например, в моей системе:

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

Первым напечатанным значением является младшие 32 бита значения указателя, возвращаемого __errno_location(). Я не знаю 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 имеет то преимущество, что вам нужно назначить его только один раз - если вы не отлаживаете многопоточную программу. В этом случае значение &errno может варьироваться в зависимости от потока, в котором вы его оцениваете.

Вероятно, это ошибка или, по крайней мере, недостающая функция, в gdb.

ОБНОВЛЕНИЕ. Комментарий Кевина Кокса предлагает обходное решение:

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) 

Ответ 4

Как говорили другие, errno не является переменной, которую может печатать gdb. Но gdb может оценивать функции, а __errno_location() возвращает указатель на `errno '. Единственное, что нам нужно сделать, это вызвать функцию и разыменовать результат:

(gdb) p *__errno_location()

И что это.

Ответ 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 будет содержать ошибку.