C、clock_gettime,返回了不正确的纳秒值?

Cla*_*oro 2 c posix gettime timer

我正在编写一个简单的程序,它检查经过的时间是否超过 1 秒。我使用clock_gettime()获取开始时间,然后调用sleep(5),获取新时间并检查差异是否大于1;我睡了 5 秒,那么它应该大于 5,但我的程序打印了一个奇怪的结果。

这是代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

int main()
{
    struct timespec tv1,tv3,expected;
    struct timespec nano1;

    tv1.tv_nsec = 0;
    tv3.tv_nsec = 0;

    expected.tv_nsec = 400000;

    if(clock_gettime(CLOCK_MONOTONIC,&tv3) == -1){
        perror(NULL);
    }

    sleep(5);

    if(clock_gettime(CLOCK_MONOTONIC,&tv1) == -1){
        perror(NULL);
    }


    long nsec = tv1.tv_nsec - tv3.tv_nsec;

    if(nsec>expected.tv_nsec){
        printf("nsec runned: %ld   nsec timeout: %ld\n",nsec,expected.tv_nsec);
    }


    printf("elapsed nanoseconds: %ld\n",nsec);


    if((nsec>expected.tv_nsec))
        printf("expired timer\n");

    else
        printf("not expired timer\n");

    exit(EXIT_SUCCESS);
}
Run Code Online (Sandbox Code Playgroud)

我的程序的输出是:

“经过的纳秒:145130”和“未过期超时”

哪里有问题?

Jon*_*ler 5

a 中表示的时间struct timespec有两个组成部分:

\n\n
    \n
  • tv_sec\xe2\x80\x94time_t整数秒值。
  • \n
  • tv_nsec\xe2\x80\x94 纳秒数的 32 位整数,0..999,999,999
  • \n
\n\n

您的计算没有考虑值之间的差异tv_sec。纳秒值之间的差异如您所说的那么大,这有点令人惊讶,但绝非不可能。为了获得整体差异,您需要同时考虑tv_sectv_nsec组件。

\n\n

sub_timespec()

\n\n

您可以使用如下函数减去两个值(以获得差值):

\n\n
enum { NS_PER_SECOND = 1000000000 };\n\nvoid sub_timespec(struct timespec t1, struct timespec t2, struct timespec *td)\n{\n    td->tv_nsec = t2.tv_nsec - t1.tv_nsec;\n    td->tv_sec  = t2.tv_sec - t1.tv_sec;\n    if (td->tv_sec > 0 && td->tv_nsec < 0)\n    {\n        td->tv_nsec += NS_PER_SECOND;\n        td->tv_sec--;\n    }\n    else if (td->tv_sec < 0 && td->tv_nsec > 0)\n    {\n        td->tv_nsec -= NS_PER_SECOND;\n        td->tv_sec++;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

fmt_timespec

\n\n

您可以使用如下函数将其格式化为具有指定小数位数的浮点值:

\n\n
int fmt_timespec(const struct timespec *value, int dp, char *buffer, size_t buflen)\n{\n    assert(value != 0 && buffer != 0 && buflen != 0);\n    if (value == 0 || buffer == 0 || buflen == 0)\n    {\n        errno = EINVAL;\n        return -1;\n    }\n    assert(dp >= 0 && dp <= 9);\n    if (dp < 0 || dp > 9)\n    {\n        errno = EINVAL;\n        return -1;\n    }\n    if ((value->tv_sec > 0 && value->tv_nsec < 0) ||\n        (value->tv_sec < 0 && value->tv_nsec > 0))\n    {\n        /* Non-zero components of struct timespec must have same sign */\n        errno = EINVAL;\n        return -1;\n    }\n\n    int len;\n    if (dp == 0)\n        len = snprintf(buffer, buflen, "%ld", value->tv_sec);\n    else\n    {\n        long nsec = value->tv_nsec;\n        long secs = value->tv_sec;\n        const char *sign = (secs < 0 || (secs == 0 && nsec < 0)) ? "-" : "";\n        if (secs < 0)\n            secs = -secs;\n        if (nsec < 0)\n            nsec = -nsec;\n        for (int i = 0; i < 9 - dp; i++)\n            nsec /= 10;\n        len = snprintf(buffer, buflen, "%s%ld.%.*ld", sign, secs, dp, nsec);\n    }\n    if (len > 0 && (size_t)len < buflen)\n        return len;\n    errno = EINVAL;\n    return -1;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

有问题的代码的修订版本

\n\n

标头time_io.h声明了格式和扫描功能struct timespec;标time_math.h头声明了用于添加和减去值的函数struct timespec。拥有这么多标头可能会过度划分代码。

\n\n
#include "time_io.h"\n#include "time_math.h"\n#include <inttypes.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n#include <unistd.h>\n\nenum { NS_PER_SECOND = 1000000000 };\n\nint main(void)\n{\n    struct timespec tv3;\n    if (clock_gettime(CLOCK_MONOTONIC, &tv3) == -1)\n        perror("clock_gettime()");\n\n    sleep(5);\n\n    struct timespec tv1;\n    if (clock_gettime(CLOCK_MONOTONIC, &tv1) == -1)\n        perror("clock_gettime()");\n\n    struct timespec td;\n    sub_timespec(tv3, tv1, &td);\n\n    int64_t ts_in_ns = td.tv_sec * NS_PER_SECOND + td.tv_nsec;\n\n    char buffer[32];\n    fmt_timespec(&td, 9, buffer, sizeof(buffer));\n\n    printf("Elapsed time: %s (%" PRId64 " nanoseconds)\\n", buffer, ts_in_ns);\n\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

运行示例:

\n\n
Elapsed time: 5.005192000 (5005192000 nanoseconds)\n
Run Code Online (Sandbox Code Playgroud)\n\n

在运行 macOS Sierra 10.12.6 的 Mac 上(最终有clock_gettime()\xe2\x80\x94 早期版本的 Mac OS X 不支持它),分辨率为clock_gettime()1000 纳秒,实际上是微秒。因此,在 Mac 上,最后 3 位小数始终为零。

\n\n

add_timespec()

\n\n

为了完整起见,您可以添加两个struct timespec值:

\n\n
void add_timespec(struct timespec t1, struct timespec t2, struct timespec *td)\n{\n    td->tv_nsec = t2.tv_nsec + t1.tv_nsec;\n    td->tv_sec  = t2.tv_sec + t1.tv_sec;\n    if (td->tv_nsec >= NS_PER_SECOND)\n    {\n        td->tv_nsec -= NS_PER_SECOND;\n        td->tv_sec++;\n    }\n    else if (td->tv_nsec <= -NS_PER_SECOND)\n    {\n        td->tv_nsec += NS_PER_SECOND;\n        td->tv_sec--;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

scn_timespec()

\n\n

并且“扫描”过程更加混乱(输入通常比输出更混乱):

\n\n
int scn_timespec(const char *str, struct timespec *value)\n{\n    assert(str != 0 && value != 0);\n    if (str == 0 || value == 0)\n    {\n        errno = EINVAL;\n        return -1;\n    }\n    long sec;\n    long nsec = 0;\n    int sign = +1;\n    char *end;\n    /* No library routine sets errno to 0 - but this one needs to */\n    int old_errno = errno;\n\n    errno = 0;\n\n    /* Skip leading white space */\n    while (isspace((unsigned char)*str))\n        str++;\n\n    /* Detect optional sign */\n    if (*str == \'+\')\n        str++;\n    else if (*str == \'-\')\n    {\n        sign = -1;\n        str++;\n    }\n\n    /* Next character must be a digit */\n    if (!isdigit((unsigned char)*str))\n    {\n        errno = EINVAL;\n        return -1;\n    }\n\n    /* Convert seconds part of string */\n    sec = strtol(str, &end, 10);\n    if (end == str || ((sec == LONG_MAX || sec == LONG_MIN) && errno == ERANGE))\n    {\n        errno = EINVAL;\n        return -1;\n    }\n\n    if (*end != \'\\0\' && !isspace((unsigned char)*end))\n    {\n        if (*end++ != \'.\')\n        {\n            errno = EINVAL;\n            return -1;\n        }\n        if (*end == \'\\0\')\n            nsec = 0;\n        else if (isdigit((unsigned char)*end))\n        {\n            char *frac = end;\n            nsec = strtol(frac, &end, 10);\n            if (end == str ||\n                ((nsec == LONG_MAX || nsec == LONG_MIN) && errno == ERANGE) ||\n                (nsec < 0 || nsec >= NS_PER_SECOND) || (end - frac > 9))\n            {\n                errno = EINVAL;\n                return -1;\n            }\n            for (int i = 0; i < 9 - (end - frac); i++)\n                nsec *= 10;\n        }\n    }\n\n    /* Allow trailing white space - only */\n    unsigned char uc;\n    while ((uc = (unsigned char)*end++) != \'\\0\')\n    {\n        if (!isspace(uc))\n        {\n            errno = EINVAL;\n            return -1;\n        }\n    }\n\n    /* Success! */\n    value->tv_sec = sec * sign;\n    value->tv_nsec = nsec * sign;\n    errno = old_errno;\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n