如何使用printf正确地打印一个clock_t?

36

我目前在使用显式转换为unsigned long long,并使用%llu打印它,但由于size_t%z说明符,为什么clock_t没有呢?

甚至没有一个宏来支持。也许我可以假设在x64系统上(操作系统和CPU),size_t的长度为8字节(即使在这种情况下,他们也提供了%z),但是clock_t呢?

5个回答

33

似乎没有完美的方法。问题的根源在于clock_t既可以是整数也可以是浮点数。

clock_t可以是浮点类型

正如Bastien Léonard提到POSIX(请为他投票),C99 N1256 draft7.23.1/3也指出:

[clock_t是]能够表示时间的算术类型

以及6.2.5/18:

整数和浮点类型被统称为算术类型。

标准将算术类型定义为整数或浮点类型之一。

如果要除以CLOCKS_PER_SEC,请使用long double

clock()的返回值是实现定义的,唯一的方法是除以CLOCKS_PER_SEC来找到秒数的标准意义:

clock_t t0 = clock();
/* Work. */
clock_t t1 = clock();
printf("%Lf", (long double)(t1 - t0));

这已经足够好了,虽然不完美,但有以下两个原因:

  • 似乎没有类似于intmax_t的浮点类型:如何获取实现中最大精度的浮点数据类型及其printf说明符? 因此,如果明天出现更大的浮点类型,则可以使用并破坏您的实现。

  • 如果clock_t是整数,则将其转换为浮点数定义良好,以使用最接近的可能浮点数。您可能会失去精度,但与绝对值相比,这并不重要,并且只会在大量时间(例如,在x86中的long int是具有64位有效数字的80位浮点数,相当于数百万年)内发生。

赞同lemonad说了类似的话。

如果您认为它是整数,请使用%ju和uintmax_t

尽管unsigned long long目前是最大的标准整数类型:

因此,最好将其转换为最大的无符号整数类型:

#include <stdint.h>

printf("%ju", (uintmax_t)(clock_t)1);

uintmax_t保证具有机器上可能的最大整数大小。

uintmax_t及其printf说明符%ju是在c99中引入的,例如gcc实现了它们。

额外的好处是,这解决了如何可靠地printf整数类型的问题(对于clock_t来说,这不一定是必需的)。

如果是double,会出现什么问题:

  • 如果太大而无法适应整数,则行为未定义
  • 比1小得多,将四舍五入为0,您将看不到任何东西

由于这些后果比整数转换成浮点数更为严重,因此使用float可能是一个更好的选择。

在glibc 2.21上,它是整数

手册说使用double是一个更好的选择:

在GNU/Linux和GNU/Hurd系统中,clock_t等价于long int,而CLOCKS_PER_SEC是一个整数值。但在其他系统中,clock_t和宏CLOCKS_PER_SEC都可以是整数或浮点类型。将CPU时间值转换为double,就像上面的例子一样,可以确保算术和打印等操作在任何基础表示下都能正常且一致地工作。
在glibc 2.21中:

另请参阅


3
%ju?这会打印出ju - Victor
@Victor:你是用gcc -std=c99编译的吗(或者告诉你的编译器使用c99)?你的编译器版本是什么?我刚测试了一下,在我的gcc --version等于gcc (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3下,以下代码可以正常工作:printf( "printf uintmax_t = %ju\n", (uintmax_t)1 ); - Ciro Santilli OurBigBook.com
我正在使用Visual Studio 2010 :) - Victor
似乎c99不被VS2010支持。而且我也看到微软没有计划在他们的编译器上实现它(如果我错了请纠正)。我会坚持使用C++进行Windows编程。 - Ciro Santilli OurBigBook.com
1
“unsigned long long int” 不是最大的整数类型。平台可能提供更大尺寸的自定义整数类型。 - fuz
显示剩余4条评论

13

非常清晰明了,我忘记了它可能是浮点类型。 - Spidey
有没有一种方法可以获取具有最大精度的浮点类型及其说明符,就像可以使用uintmax_t%ju来获取整数一样?那将是最优的方式。 - Ciro Santilli OurBigBook.com
@cirosantilli: 似乎没有 - Ciro Santilli OurBigBook.com

12

可能是因为时钟滴答不是一个非常明确定义的单位。您可以将其转换为秒并打印为double:

time_in_seconds = (double)time_in_clock_ticks / (double)CLOCKS_PER_SEC;
printf("%g seconds", seconds);

CLOCKS_PER_SEC宏会展开为一个表达式,表示每秒钟时钟计时器的数目。


我宁愿使用1)long double,因为它可能具有更高的精度2)在除法之后执行单个类型转换:(long double)(time_in_clock_ticks / CLOCKS_PER_SEC),以便只舍入一次。 - Ciro Santilli OurBigBook.com

4

C标准必须适应各种架构,这使得除了内部时钟类型是算术的这一事实外,不可能作出任何进一步的保证。

在大多数情况下,您关心的是时间间隔,因此我会将时钟滴答数的差异转换为毫秒。即使它是32位的,无符号长整型足以表示近50天的时间间隔,因此对于大多数情况来说,它应该足够大:

clock_t start;
clock_t end;
unsigned long millis = (end - start) * 1000 / CLOCKS_PER_SEC;

这正是为什么我无法想象为什么在任何头文件中都没有指定 clock_t 打印格式的宏。 - Spidey
@Spidey:但是如果您无法猜测表示方式,输出格式应该是什么呢?请记住,未指定clock_t是整数还是浮点值;如果您想要做任何有用的事情,您必须将其与CLOCKS_PER_SEC相关联,而这超出了printf()的领域。 - Christoph
@Christoph:由于clock_t未定义,据我所知,CLOCKS_PER_SEC也未定义,并且它与clock_t具有相同的类型。由于clock_t通常不会用于生产,我不关心它是什么类型,也不会依赖它进行任何解析。但我看到size_t是一些整数类型,而clock_t不是(并非总是),因此我们被迫进行显式转换。 - Spidey

1
一种方法是使用 gettimeofday 函数。 可以使用此函数找到差异:
unsigned long  diff(struct timeval second, struct timeval first)
{
    struct timeval  lapsed;
    struct timezone tzp;
    unsigned long t;

    if (first.tv_usec > second.tv_usec) {
        second.tv_usec += 1000000;
        second.tv_sec--;
    }

    lapsed.tv_usec = second.tv_usec - first.tv_usec;
    lapsed.tv_sec  = second.tv_sec  - first.tv_sec;
    t = lapsed.tv_sec*1000000 + lapsed.tv_usec;

    printf("%lu,%lu - %lu,%lu = %ld,%ld\n",
           second.tv_sec, second.tv_usec,
           first.tv_sec,  first.tv_usec,
           lapsed.tv_sec, lapsed.tv_usec);

    return t;
}

POSIX.1-2008将gettimeofday()标记为过时,这可能需要某些预处理指令才能使编译器引用它们,如果您还使用像-DXOPEN_SOURCE=600等东西,则需要这些指令。 - JohnH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接