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