【问题标题】:Linux system call time() is returning ((time_t) -14) on errorLinux 系统调用 time() 出错时返回 ((time_t) -14)
【发布时间】:2015-02-02 20:37:14
【问题描述】:

手册页man 2 time 说:

SYNOPSIS
       #include <time.h>
       time_t time(time_t *t);

RETURN VALUE
       On success, the value of time in seconds since  the  Epoch  is  returned.   On  error,
       ((time_t) -1) is returned, and errno is set appropriately.

ERRORS
       EFAULT t points outside your accessible address space.

我有一个简单的测试设置,试图从该函数中强制出错 - 应该是有史以来最直接的事情:

time_t *ptr = (time_t *) 0xabad1dea;
time_t secs = time(ptr);

if (secs == ((time_t) -1)) /* Not caught */
    exit(EXIT_FAILURE);

printf("Number of seconds since the Epoch:        %lld\n", secs);
printf("It should return ((time_t) -1) on error:  %lld\n", (time_t) -1);
secs = *ptr; /* This *does* segfault */

由于某种原因,time() 在我的系统上返回 -14 而不是 -1。我完全被难住了。甚至感到困惑。

我怀疑任何人都可以重现此问题,但有没有人知道我到底为什么会出现这种行为?如果是这样,我非常好奇!

编辑:我知道时间并没有取消引用我传递的错误指针,因为这会使程序崩溃。手册页确实说如果地址不可访问,它应该返回 -1 并设置 errno。 EFAULT 为 14,但没有返回 -EFAULT not 此处定义的 time() 是什么?

【问题讨论】:

  • 正如预期的那样,你得到了 EFAULT,这在 linux 上是错误 #14
  • @MarcB 可能是它,但它会违反手册,其中指出 errno 设置为 EFAULT 并返回 (time_t)-1。
  • @MarcB:可能需要将errno 设置为EFAULT。 返回 -EFAULT 不是。
  • "%lld" 需要 long long 类型的参数。尝试转换参数:printf("... %lld\n", (long long)secs);。唔。如果time_t 比您系统上的long long 窄,printf 可能会从相邻内存中获取-14。

标签: c linux time system-calls


【解决方案1】:

根据文档time(2):

time_t 时间(time_t *t);

如果 t 不为 NULL,则返回值也存储在 t 指向的内存中。

在您的通话中,您传递了一些随机地址0xabad1dea。你很幸运,它只返回 -14 而没有做更糟糕的事情。传递 NULL 或变量的地址。

【讨论】:

  • 我有预感 OP 知道 0xABad1dea 很讨厌
【解决方案2】:

至少有 3 个不同的地方记录了 time() 函数的行为。

ISO C standard 说:

time 函数返回实现的最佳近似值 当前日历时间。如果返回值 (time_t)(-1) 日历时间不可用。如果 timer 不是空指针,则 返回值也分配给它指向的对象。

标准中的其他措辞清楚地表明,传递无效参数(这就是您正在执行的操作)具有未定义的行为。 time() 需要处理空指针,但不能处理无效指针。

POSIX says:

成功完成后,time() 将返回时间值。 否则返回 (time_t)-1。

这不允许除当前时间或(time_t)(-1) 以外的结果。另一方面,POSIX 也说:

此参考页面上描述的功能与 ISO C 标准。此处描述的要求之间的任何冲突 ISO C标准是无意的。本卷 POSIX.1-2008 遵循 ISO C 标准。

这可能意味着time() 带有无效参数的行为是未定义的,就像在 C 中一样。

Linux 手册页说:

成功时,返回自 Epoch 以来的时间值(以秒为单位)。 出错时,返回 ((time_t) -1),并正确设置 errno。

它还明确指出,如果“t 指向您可访问的地址空间之外”,则将 errno 设置为 EFAULT。因此,它定义了标准未定义行为的情况下的行为,这对于实现来说是完全有效的。

奇怪的是它返回 -14 (-EFAULT) 而不是将errno 设置为EFAULT。返回否定的errno 值是Linux 内核中函数 的常见约定。 C 运行时中的包装器通常会否定该返回值并使用它来设置errno,将返回值设置为-1(或为特定函数定义的任何错误值)。这就是我希望在这里发生的事情。

另一件奇怪的事情:在我的系统(Linux Mint 17,x86_64)上,使用无效指针调用 time() 会导致我的程序因分段错误而死。

我怀疑您的系统存在错误(有一个特别简单的解决方法:不要那样做)。


更新:我想到了另一种可能性。您的printf 调用不正确,或者至少不可移植。您正在使用"%lld" 格式打印time_t 值,这需要long long 类型的参数。您应该将参数转换为long long:

printf("Number of seconds since the Epoch:        %lld\n",
       (long long)secs);
printf("It should return ((time_t) -1) on error:  %lld\n",
       (long long)((time_t) -1));

如果time_t 和long long 在您的系统上是相同的宽度,那么它可能 可以在没有演员表的情况下工作(但无论如何您都应该添加它们)。但如果time_t 比long long 窄,printf 可能会从相邻内存中拾取杂散的-14 值。

您的系统上time_t 和long long 的大小是多少?强制转换参数会改变行为吗?

【讨论】:

  • 我认为你是对的,这只是作为时间函数中的错误才真正有意义。
  • @kebertx:也许——但请参阅我更新答案的最后一段。您还可以使用有关您的系统的更多信息来更新您的问题。
  • @KeithThompson:看看glibc.git:sysdeps/unix/sysv/linux/time.c,这是一个明显的 glibc 错误。 Linux 内核将错误值作为负码返回,而不是检查,glibc time() 实现假定它不会失败。
  • @NominalAnimal:我突然想到-14 是一个有效的结果——或者它曾经是。
  • :) 1969 年 12 月 31 日星期三 23:59:46 UTC 。假设当前时代。尽管如此,内核的 EFAULT 返回值为 -14,因此 glibc 肯定无法将其转换为 (time_t)-1 且 errno 设置为 14 (EFAULT)。
猜你喜欢
  • 2016-05-23
  • 2012-02-11
  • 2010-11-06
  • 1970-01-01
  • 1970-01-01
  • 2019-04-22
  • 1970-01-01
  • 2020-09-21
  • 2011-11-17
相关资源
最近更新 更多