2
\$\begingroup\$

The following snippet of C includes implementations for both Windows NT and POSIX-compliant systems to get the time (with microsecond resolution) since the Unix epoch. In the case of Windows NT, the time is registered as 100-nanosecond ticks since the NT epoch (1/1/1601), so it requires a bit of conversion.

I feel like this is the best solution I could've come up with, but it still feels a bit clunky. See if you can help:

#if defined(__unix__) || defined(unix) || defined(__unix) || defined(__CYGWIN__)
#include <sys/time.h>
static struct timeval lnm_current_time;
#elif defined(_WIN32) || defined(__WINDOWS__)
#include <windows.h>
#include <sysinfoapi.h>
static FILETIME lnm_win32_filetime;
#else
#error lognestmonster: Neither Windows NT nor a POSIX-compliant system were detected.\
        Implement your own system time functions or compile on a compliant system.
#endif


uint64_t lnm_getus(void) {
    uint64_t us;
#if defined(__unix__) || defined(unix) || defined(__unix) || defined(__CYGWIN__)
    gettimeofday(&lnm_current_time, NULL);
    us = (lnm_current_time.tv_sec*1000000+lnm_current_time.tv_usec);
#elif defined(_WIN32) || defined(__WINDOWS__)
    // get system time in ticks
    GetSystemTimeAsFileTime(&lnm_win32_filetime);
    // load time from two 32-bit words into one 64-bit integer
    us = lnm_win32_filetime.dwHighDateTime;
    us = us << 32;
    us |= lnm_win32_filetime.dwLowDateTime;
    // convert to microseconds
    us /= 10;
    // convert from time since Windows NT epoch to time since Unix epoch
    us -= 11644473600000000ULL;
#endif
    return us;
}
```
\$\endgroup\$
4
\$\begingroup\$

Using static file-scope variables

static struct timeval lnm_current_time;
// ...
static FILETIME lnm_win32_filetime;

makes your code thread-unsafe: Two “simultaneous” invocations of your function from different threads access the same memory. (For other potential drawbacks of static file-scope variables see for example Are file-scope static variables in C as bad as extern global variables? on Software Engineering.)

And there is no reason to use static variables here. With function local variables

uint64_t lnm_getus(void) {
    uint64_t us;
#if defined(__unix__) || defined(unix) || defined(__unix) || defined(__CYGWIN__)
    struct timeval lnm_current_time;
    gettimeofday(&lnm_current_time, NULL);
    // ...
#elif defined(_WIN32) || defined(__WINDOWS__)
    FILETIME lnm_win32_filetime;
    GetSystemTimeAsFileTime(&lnm_win32_filetime);
    // ...
#endif
    return us;
}

the variables are defined exactly where they are needed, which makes the code easier to read and to argue about, and avoids the threading problem.

\$\endgroup\$
2
\$\begingroup\$

In larger programs, lots of "compiler switches" (#ifdefs) make the code very hard to read, "clunky" if you will. It's not an issue in a tiny program such as this, but for larger programs you should consider a more "polymorphic" approach.

Like for example having a generic "epoch.h" as the platform-independent API. Link this with with a corresponding "epoch_unix.c" or "epoch_windows.c" file, where the C file contains everything OS-specific. Manage which one that gets linked using different builds and/or version control.


Some minor details:

  • You forgot #include <stdint.h>.

  • lnm_current_time.tv_sec*1000000+lnm_current_time.tv_usec. Make a habit of always declaring integer constants with a large enough type, no matter where they happen to be in an expression.

    That is, 1000000ULL, or if you prefer UINT64_C(1000000), the former giving type unsigned long long and the latter giving type uint_least64_t. The latter is ever so slightly more portable, but a bit harder to read.

\$\endgroup\$
2
  • \$\begingroup\$ The multiplication will implicitly convert from int to the appropriate type, won't it? (And it's epoch, not epoc.) \$\endgroup\$
    – S.S. Anne
    Mar 31 '20 at 16:01
  • 1
    \$\begingroup\$ @S.S.Anne Only if lnm_current_time.tv_sec is of an appropriate type. Which is hard to tell, you have to dig deep in documentation to find out just what time_t actually is. \$\endgroup\$
    – Lundin
    Apr 1 '20 at 6:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.