Keywords: C programming | timestamp | microsecond | gettimeofday | clock_gettime | timespec_get
Abstract: This article delves into various methods for obtaining microsecond-resolution timestamps in C, focusing on common pitfalls with gettimeofday and its correct implementation, while also introducing the C11 standard's timespec_get function and the superior clock_gettime function in Linux/POSIX systems. It explains timestamp composition, precision issues, clock type selection, and practical considerations, providing complete code examples and error handling mechanisms to help developers choose the most suitable timestamp acquisition strategy.
In C programming, obtaining precise timestamps is fundamental for many applications, especially in performance analysis, event ordering, and real-time systems. A common challenge is correctly acquiring microsecond-resolution timestamps while avoiding typical implementation errors. This article starts from basic concepts and progressively explores multiple solutions with technical details.
Basic Usage and Common Pitfalls of gettimeofday
Many developers first encounter the gettimeofday function, which returns a struct timeval containing seconds (tv_sec) and microseconds (tv_usec). A frequent mistake is using only the tv_usec part, leading to non-monotonic timestamps when microseconds overflow. For instance, as seconds increment, microseconds reset to zero, causing later timestamps to appear smaller if only tv_usec is compared, violating timestamp monotonicity.
The correct approach combines seconds and microseconds into a full microsecond timestamp. The following code demonstrates this:
#include <sys/time.h>
#include <stdint.h>
uint64_t get_microsecond_timestamp() {
struct timeval tv;
gettimeofday(&tv, NULL);
return (uint64_t)tv.tv_sec * 1000000ULL + (uint64_t)tv.tv_usec;
}
Using uint64_t prevents overflow issues. On 32-bit systems, with unsigned long alone, timestamps overflow after approximately 71 minutes (since 232 microseconds ≈ 4295 seconds). Conversion to 64-bit integers extends the valid range significantly for most applications.
The timespec_get Function in C11 Standard
For systems compliant with C11 or later, the timespec_get function offers a cross-platform method for timestamp acquisition. It returns a struct timespec with seconds (tv_sec) and nanoseconds (tv_nsec), providing higher resolution. Below is a complete example with error handling:
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#define SEC_TO_NS(sec) ((sec) * 1000000000ULL)
uint64_t get_nanosecond_timestamp() {
struct timespec ts;
if (timespec_get(&ts, TIME_UTC) == 0) {
fprintf(stderr, "Failed to obtain timestamp\n");
return UINT64_MAX; // Use a special value to indicate error
}
return SEC_TO_NS((uint64_t)ts.tv_sec) + (uint64_t)ts.tv_nsec;
}
Note that timespec_get does not allow clock type selection, which may limit its use in applications requiring monotonic clocks. Monotonic clocks ensure time only moves forward, unaffected by system time adjustments, crucial for interval measurements.
The clock_gettime Function in Linux/POSIX Systems
On Linux or POSIX-compliant systems, clock_gettime is a superior choice as it permits specifying clock types. For example, CLOCK_MONOTONIC_RAW provides an unadjusted monotonic clock suitable for high-precision timing. The _POSIX_C_SOURCE macro must be defined to ensure function availability.
#define _POSIX_C_SOURCE 199309L
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define SEC_TO_NS(sec) ((sec) * 1000000000ULL)
uint64_t get_monotonic_nanosecond_timestamp() {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts) == -1) {
fprintf(stderr, "Failed to obtain timestamp: %s\n", strerror(errno));
return UINT64_MAX;
}
return SEC_TO_NS((uint64_t)ts.tv_sec) + (uint64_t)ts.tv_nsec;
}
Common clock types include: CLOCK_REALTIME (system real-time, subject to adjustments), CLOCK_MONOTONIC (monotonic but may be influenced by NTP), and CLOCK_MONOTONIC_RAW (raw monotonic, immune to external adjustments). Selecting the appropriate clock based on application needs is key.
Timestamp Precision and Resolution
Timestamp precision depends not only on the function but also on hardware and system factors. Theoretically, clock_getres can query clock resolution, but actual effective resolution may be lower. By rapidly sampling timestamps and computing the minimum interval, one can estimate practical resolution. For instance, on some systems, clock_gettime may achieve resolutions between 14 and 130 nanoseconds.
The following code illustrates simple microsecond and millisecond timestamp functions based on clock_gettime:
#define _POSIX_C_SOURCE 199309L
#include <time.h>
#include <stdint.h>
#define SEC_TO_MS(sec) ((sec) * 1000ULL)
#define SEC_TO_US(sec) ((sec) * 1000000ULL)
#define NS_TO_MS(ns) ((ns) / 1000000ULL)
#define NS_TO_US(ns) ((ns) / 1000ULL)
uint64_t millis() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
return SEC_TO_MS((uint64_t)ts.tv_sec) + NS_TO_MS((uint64_t)ts.tv_nsec);
}
uint64_t micros() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
return SEC_TO_US((uint64_t)ts.tv_sec) + NS_TO_US((uint64_t)ts.tv_nsec);
}
These functions offer convenient interfaces, but overflow concerns must be noted. On 32-bit systems, tv_sec might be a 32-bit signed integer, overflowing in about 136 years. Using 64-bit types mitigates this issue.
Practical Recommendations and Summary
When choosing a timestamp method, consider: system compatibility (e.g., POSIX support), precision requirements (microseconds or nanoseconds), clock type (monotonicity needs), and overflow risks. For simple applications, combining timestamps with gettimeofday suffices; for high-precision or cross-platform needs, clock_gettime (on available systems) or timespec_get (on C11-compliant systems) is recommended.
Error handling is crucial in timestamp acquisition. Functions may fail due to insufficient permissions or system errors, returning error codes or special values (e.g., UINT64_MAX) aids debugging. Additionally, testing timestamp resolution and monotonicity in practice ensures reliability in specific environments.
By understanding the underlying principles and various implementations of timestamps, developers can more effectively integrate timing functionalities into their C projects, enhancing code robustness and performance.