This is the function (this one is found in many places online)
time_t mkgmtime(struct tm *ptm)
{
time_t secs=0;
// tm_year is years since 1900
int year=ptm->tm_year + 1900;
for (int y=1970; y < year; ++y)
{
secs+=(IsLeapYear(y) ? 366 : 365) * SecondsPerDay;
}
// tm_mon is month from 0..11
for (int m=0; m < ptm->tm_mon; ++m)
{
secs+=NtpDaysPerMonth[m] * SecondsPerDay;
if (m == 1 && IsLeapYear(year))
secs+=SecondsPerDay;
}
secs+=(ptm->tm_mday - 1) * SecondsPerDay;
secs+=ptm->tm_hour * SecondsPerHour;
secs+=ptm->tm_min * SecondsPerMinute;
secs+=ptm->tm_sec;
return secs;
}
It is pretty obvious the intention is that time_t is a uint64_t, but it appears to be ultimately defined as a long long int which is a int64_t on this device. Of course an int64_t is not the same as a uint64_t but it probably does not matter given the vast span of 64 bits.
The .h file contains this
time_t mkgmtime(struct tm *ptm);
time.h is included above this .h file and contains this
/*
* time.h
*
* Struct and function declarations for dealing with time.
*/
#ifndef _TIME_H_
#define _TIME_H_
#include "_ansi.h"
#include <sys/cdefs.h>
#include <sys/reent.h>
#define __need_size_t
#define __need_NULL
#include <stddef.h>
/* Get _CLOCKS_PER_SEC_ */
#include <machine/time.h>
#ifndef _CLOCKS_PER_SEC_
#define _CLOCKS_PER_SEC_ 1000
#endif
#define CLOCKS_PER_SEC _CLOCKS_PER_SEC_
#define CLK_TCK CLOCKS_PER_SEC
#include <sys/types.h>
#include <sys/timespec.h>
#if __POSIX_VISIBLE >= 200809
#include <sys/_locale.h>
#endif
_BEGIN_STD_C
struct tm
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
#ifdef __TM_GMTOFF
long __TM_GMTOFF;
#endif
#ifdef __TM_ZONE
const char *__TM_ZONE;
#endif
};
clock_t clock (void);
double difftime (time_t _time2, time_t _time1);
time_t mktime (struct tm *_timeptr);
time_t time (time_t *_timer);
#ifndef _REENT_ONLY
char *asctime (const struct tm *_tblock);
char *ctime (const time_t *_time);
struct tm *gmtime (const time_t *_timer);
struct tm *localtime (const time_t *_timer);
#endif
size_t strftime (char *__restrict _s,
size_t _maxsize, const char *__restrict _fmt,
const struct tm *__restrict _t);
#if __POSIX_VISIBLE >= 200809
extern size_t strftime_l (char *__restrict _s, size_t _maxsize,
const char *__restrict _fmt,
const struct tm *__restrict _t, locale_t _l);
#endif
char *asctime_r (const struct tm *__restrict,
char *__restrict);
char *ctime_r (const time_t *, char *);
struct tm *gmtime_r (const time_t *__restrict,
struct tm *__restrict);
struct tm *localtime_r (const time_t *__restrict,
struct tm *__restrict);
_END_STD_C
#ifdef __cplusplus
extern "C" {
#endif
#if __XSI_VISIBLE
char *strptime (const char *__restrict,
const char *__restrict,
struct tm *__restrict);
#endif
#if __GNU_VISIBLE
char *strptime_l (const char *__restrict, const char *__restrict,
struct tm *__restrict, locale_t);
#endif
#if __POSIX_VISIBLE
void tzset (void);
#endif
void _tzset_r (struct _reent *);
typedef struct __tzrule_struct
{
char ch;
int month; /* Month of year if ch=M */
int week; /* Week of month if ch=M */
int day; /* Day of week if ch=M, day of year if ch=J or ch=D */
int secs; /* Time of day in seconds */
time_t change;
long offset; /* Match type of _timezone. */
} __tzrule_type;
typedef struct __tzinfo_struct
{
int __tznorth;
int __tzyear;
__tzrule_type __tzrule[2];
} __tzinfo_type;
__tzinfo_type *__gettzinfo (void);
/* getdate functions */
#ifdef HAVE_GETDATE
#if __XSI_VISIBLE >= 4
#ifndef _REENT_ONLY
#define getdate_err (*__getdate_err())
int *__getdate_err (void);
struct tm * getdate (const char *);
/* getdate_err is set to one of the following values to indicate the error.
1 the DATEMSK environment variable is null or undefined,
2 the template file cannot be opened for reading,
3 failed to get file status information,
4 the template file is not a regular file,
5 an error is encountered while reading the template file,
6 memory allication failed (not enough memory available),
7 there is no line in the template that matches the input,
8 invalid input specification */
#endif /* !_REENT_ONLY */
#endif /* __XSI_VISIBLE >= 4 */
#if __GNU_VISIBLE
/* getdate_r returns the error code as above */
int getdate_r (const char *, struct tm *);
#endif /* __GNU_VISIBLE */
#endif /* HAVE_GETDATE */
/* defines for the opengroup specifications Derived from Issue 1 of the SVID. */
#if __SVID_VISIBLE || __XSI_VISIBLE
extern __IMPORT long _timezone;
extern __IMPORT int _daylight;
#endif
#if __POSIX_VISIBLE
extern __IMPORT char *_tzname[2];
/* POSIX defines the external tzname being defined in time.h */
#ifndef tzname
#define tzname _tzname
#endif
#endif /* __POSIX_VISIBLE */
#ifdef __cplusplus
}
#endif
#include <sys/features.h>
#ifdef __CYGWIN__
#include <cygwin/time.h>
#endif /*__CYGWIN__*/
#if defined(_POSIX_TIMERS)
#include <signal.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Clocks, P1003.1b-1993, p. 263 */
int clock_settime (clockid_t clock_id, const struct timespec *tp);
int clock_gettime (clockid_t clock_id, struct timespec *tp);
int clock_getres (clockid_t clock_id, struct timespec *res);
/* Create a Per-Process Timer, P1003.1b-1993, p. 264 */
int timer_create (clockid_t clock_id,
struct sigevent *__restrict evp,
timer_t *__restrict timerid);
/* Delete a Per_process Timer, P1003.1b-1993, p. 266 */
int timer_delete (timer_t timerid);
/* Per-Process Timers, P1003.1b-1993, p. 267 */
int timer_settime (timer_t timerid, int flags,
const struct itimerspec *__restrict value,
struct itimerspec *__restrict ovalue);
int timer_gettime (timer_t timerid, struct itimerspec *value);
int timer_getoverrun (timer_t timerid);
/* High Resolution Sleep, P1003.1b-1993, p. 269 */
int nanosleep (const struct timespec *rqtp, struct timespec *rmtp);
#ifdef __cplusplus
}
#endif
#endif /* _POSIX_TIMERS */
#if defined(_POSIX_CLOCK_SELECTION)
#ifdef __cplusplus
extern "C" {
#endif
int clock_nanosleep (clockid_t clock_id, int flags,
const struct timespec *rqtp, struct timespec *rmtp);
#ifdef __cplusplus
}
#endif
#endif /* _POSIX_CLOCK_SELECTION */
#ifdef __cplusplus
extern "C" {
#endif
/* CPU-time Clock Attributes, P1003.4b/D8, p. 54 */
/* values for the clock enable attribute */
#define CLOCK_ENABLED 1 /* clock is enabled, i.e. counting execution time */
#define CLOCK_DISABLED 0 /* clock is disabled */
/* values for the pthread cputime_clock_allowed attribute */
#define CLOCK_ALLOWED 1 /* If a thread is created with this value a */
/* CPU-time clock attached to that thread */
/* shall be accessible. */
#define CLOCK_DISALLOWED 0 /* If a thread is created with this value, the */
/* thread shall not have a CPU-time clock */
/* accessible. */
/* Flag indicating time is "absolute" with respect to the clock
associated with a time. Value 4 is historic. */
#define TIMER_ABSTIME 4
/* Manifest Constants, P1003.1b-1993, p. 262 */
#if __GNU_VISIBLE
#define CLOCK_REALTIME_COARSE ((clockid_t) 0)
#endif
#define CLOCK_REALTIME ((clockid_t) 1)
/* Manifest Constants, P1003.4b/D8, p. 55 */
#if defined(_POSIX_CPUTIME)
/* When used in a clock or timer function call, this is interpreted as
the identifier of the CPU_time clock associated with the PROCESS
making the function call. */
#define CLOCK_PROCESS_CPUTIME_ID ((clockid_t) 2)
#endif
#if defined(_POSIX_THREAD_CPUTIME)
/* When used in a clock or timer function call, this is interpreted as
the identifier of the CPU_time clock associated with the THREAD
making the function call. */
#define CLOCK_THREAD_CPUTIME_ID ((clockid_t) 3)
#endif
#if defined(_POSIX_MONOTONIC_CLOCK)
/* The identifier for the system-wide monotonic clock, which is defined
* as a clock whose value cannot be set via clock_settime() and which
* cannot have backward clock jumps. */
#define CLOCK_MONOTONIC ((clockid_t) 4)
#endif
#if __GNU_VISIBLE
#define CLOCK_MONOTONIC_RAW ((clockid_t) 5)
#define CLOCK_MONOTONIC_COARSE ((clockid_t) 6)
#define CLOCK_BOOTTIME ((clockid_t) 7)
#define CLOCK_REALTIME_ALARM ((clockid_t) 8)
#define CLOCK_BOOTTIME_ALARM ((clockid_t) 9)
#endif
#if defined(_POSIX_CPUTIME)
/* Accessing a Process CPU-time CLock, P1003.4b/D8, p. 55 */
int clock_getcpuclockid (pid_t pid, clockid_t *clock_id);
#endif /* _POSIX_CPUTIME */
#if defined(_POSIX_CPUTIME) || defined(_POSIX_THREAD_CPUTIME)
/* CPU-time Clock Attribute Access, P1003.4b/D8, p. 56 */
int clock_setenable_attr (clockid_t clock_id, int attr);
int clock_getenable_attr (clockid_t clock_id, int *attr);
#endif /* _POSIX_CPUTIME or _POSIX_THREAD_CPUTIME */
#ifdef __cplusplus
}
#endif
#endif /* _TIME_H_ */
which confuses me because a load of the prototypes in there are also using time_t, and no errors are generated even if I edit the file so as to force it to be recompiled.
This is the error
In file included from ../src/main.c:46:
C:/KDE420/Project1/inc/KDE_NTP.h:15:1: error: unknown type name 'time_t'; did you mean 'size_t'?
15 | time_t mkgmtime(struct tm *ptm);
| ^~~~~~
| size_t
make[1]: *** [src/subdir.mk:145: src/main.o] Error 1
which also confuses me because this module is not main.c. Line 46 in main.c is
#include "KDE_NTP.h"
Probably the reason for the main.c reference is that kde_ntp.h appears in several .c files and the compiler suppresses identical errors after the first one. EDIT: that was it - time.h was not being #included in main.c :)
One thing I'd still like to know if whether is a way to trace the chain which the compiler has followed to find (or not find) some definition. I've had so many cases, over time (no pun intended), where the IDE finds a definition in some .h file which is #included but the compiler doesn't, but later it magically does.
Perhaps a simple example might be illustrative here.
Because of "stuff", I decided I wanted a simple C/C++ implementation of efficient (hardware-accelerated, if possible) 3- and 4-component vectors of 32-bit integers, and basic arithmetic operations (addition, subtraction, multiplication, division, dot product, and three-component cross product).
Note: This requires compiler-specific extensions, but those are available at least in GCC, clang, and Intel CC. This is my design choice here; I chose to trade some portability for the efficiency of the generated code on SIMD-capable architectures in an architecture-agnostic manner.
Anyway, I started by creating a wrapper header file, ivec.h:
#ifndef IVEC_H
#define IVEC_H
/*
* Four-component hardware-accelerated vector type support
*
* The r component is first, followed by the x, y, and z components.
*/
#include <stdint.h>
#define ALT4(_1, _2, _3, _4, NAME, ...) NAME
#if defined(__GNUC__)
/* GCC has vector support built-in as an extension to C. */
typedef int32_t ivec __attribute__((vector_size (4 * sizeof (int32_t))));
#ifndef PURE_ACCESSOR
#define PURE_ACCESSOR __attribute__((unused, const, always_inline)) static inline
#endif
#include "ivec_internal.h"
#elif defined(__clang__)
/* Clang has vector support built-in as an extension to C. */
typedef int32_t ivec __attribute__((vector_size (4 * sizeof (int32_t))));
#ifndef PURE_ACCESSOR
#define PURE_ACCESSOR __attribute__((unused, const, always_inline)) static inline
#endif
#include "ivec_internal.h"
#else
/* Unsupported compiler. */
#error File "ivec.h" does not contain an implementation for this compiler!
#endif
#endif /* IVEC_H */
Because the header file is wrapped in #ifndef IVEC_H, #define IVEC_H, and #endif /* IVEC_H */, it can be included safely more than once. The idea is, that any source or header file that needs the types or functionality, does an #include "ivec.h".
To detect the compiler, we can use pre-defined compiler macros (https://sourceforge.net/p/predef/wiki/Compilers/). Here, __GNUC__ is defined if compiling with GCC, and __clang__ if compiling with clang. Recent versions of Intel C++ also defines __GNUC__, so if one wanted to add specific quirks for it, one would need to test for __INTEL_COMPILER first. I don't have it installed on this particular machine –– only some versions of gcc and clang ––, so I omitted it here.
The ALT4() macro is used to choose macros or functions depending on the number of arguments (2, 3, or 4). It's one of those "tricks" you sometimes see that seem really odd, but they really are just an useful preprocessor trick. The way it is used, is
#define generic_name(...) ALT4(__VA_ARGS__, four_args, three_args, two_args)(__VA_ARGS__)
so that generic_name(a,b) expands to two_args(a,b); generic_name(a,b,c) expands to three_args(a,b,c); and generic_name(a,b,c,d) expands to four_args(a,b,c,d). This works in both C and C++.
The PURE_ACCESSOR macro should evaluate to static or static inline with compiler-specific attributes that describe the functions as only operating on its parameters (not accessing any other objects at all), with calls having the same arguments always producing the same results. The intent is to help the compiler maximally optimize these pure accessor type helper functions, without affecting the results.
All compilers currently only need the same ivec_internal.h:
#ifndef IVEC_H
#error Include "ivec.h", never "ivec_internal.h" directly!
#endif
#ifndef IVEC_INTERNAL_H
#define IVEC_INTERNAL_H
#include <stdint.h>
#define IVEC4(x, y, z, r) ((ivec){ r, x, y, z })
#define IVEC3(x, y, z) ((ivec){ 0, x, y, z })
#define IVEC2(x, y) ((ivec){ 0, x, y, 0 })
#define IVEC(...) ALT4(__VA_ARGS__, IVEC4, IVEC3, IVEC2)(__VA_ARGS__)
#define IVEC_R(a) ((a)[0])
#define IVEC_X(a) ((a)[1])
#define IVEC_Y(a) ((a)[2])
#define IVEC_Z(a) ((a)[3])
/* Accessors */
PURE_ACCESSOR int32_t ivec_r(const ivec a) { return a[0]; }
PURE_ACCESSOR int32_t ivec_x(const ivec a) { return a[1]; }
PURE_ACCESSOR int32_t ivec_y(const ivec a) { return a[2]; }
PURE_ACCESSOR int32_t ivec_z(const ivec a) { return a[3]; }
/* Definition (in a function form) */
PURE_ACCESSOR ivec ivec4_def(const int32_t x,
const int32_t y,
const int32_t z,
const int32_t r)
{
const ivec result = { r, x, y, z };
return result;
}
/* Definition (in a function form) */
PURE_ACCESSOR ivec ivec3_def(const int32_t x,
const int32_t y,
const int32_t z)
{
const ivec result = { 0, x, y, z };
return result;
}
/* Definition (in a function form) */
PURE_ACCESSOR ivec ivec2_def(const int32_t x,
const int32_t y)
{
const ivec result = { 0, x, y, 0 };
return result;
}
/* Pick definition function based on number of arguments. */
#define ivec_def(...) ALT4(__VA_ARGS__, ivec4_def, ivec3_def, ivec2_def)(__VA_ARGS__)
/* Component-wise addition */
PURE_ACCESSOR ivec ivec_add(const ivec a, const ivec b) { return a + b; }
/* Component-wise subtraction */
PURE_ACCESSOR ivec ivec_sub(const ivec a, const ivec b) { return a - b; }
/* Component-wise multiplication */
PURE_ACCESSOR ivec ivec_mul_ivec(const ivec a, const ivec b) { return a * b; }
/* Component-wise division */
PURE_ACCESSOR ivec ivec_div_ivec(const ivec a, const ivec b) { return a / b; }
/* Component-wise multiplication by a scalar */
PURE_ACCESSOR ivec ivec_mul_i32(const ivec a, const int32_t s)
{
const ivec b = { s, s, s, s };
return a * b;
}
/* Component-wise division by a scalar */
PURE_ACCESSOR ivec ivec_div_i32(const ivec a, const int32_t s)
{
const ivec b = { s, s, s, s };
return a / b;
}
#ifdef __cplusplus
PURE_ACCESSOR ivec ivec_mul(const ivec a, const ivec b) { return ivec_mul_ivec(a, b); }
PURE_ACCESSOR ivec ivec_mul(const ivec a, const int32_t b) { return ivec_mul_i32(a, b); }
PURE_ACCESSOR ivec ivec_div(const ivec a, const ivec b) { return ivec_div_ivec(a, b); }
PURE_ACCESSOR ivec ivec_div(const ivec a, const int32_t b) { return ivec_div_i32(a, b); }
#else
/* Generic multiplication depends on the second parameter */
#define ivec_mul(a, b) _Generic((b), \
ivec: ivec_mul_ivec, \
default: ivec_mul_i32 )(a, b)
/* Generic division depends on the second parameter */
#define ivec_div(a, b) _Generic((b), \
ivec: ivec_div_ivec, \
default: ivec_div_i32 )(a, b)
#endif
/* Dot product of the three first components */
PURE_ACCESSOR int32_t ivec_dot3(const ivec a, const ivec b)
{
const ivec v = a * b;
return v[1] + v[2] + v[3];
}
/* Dot product of all four components */
PURE_ACCESSOR int32_t ivec_dot4(const ivec a, const ivec b)
{
const ivec v = a * b;
return v[0] + v[1] + v[2] + v[3];
}
/* Cross product of the three first components; fourth component zero */
PURE_ACCESSOR ivec ivec_cross3(const ivec a, const ivec b)
{
const ivec apos = { 0, a[2], a[3], a[1] },
bpos = { 0, b[3], b[1], b[2] },
aneg = { 0, a[3], a[1], a[2] },
bneg = { 0, b[2], b[3], b[1] };
return apos*bpos - aneg*bneg;
}
#endif /* IVEC_INTERNAL_H */
It first verifies that IVEC_H is defined, i.e. that this file was included through ivec.h and not directly. It is also protected against multiple inclusion via IVEC_INTERNAL_H, which should not happen unless ivec.h has a bug, so the IVEC_H check alone would definitely be enough for now. (The reason I have it, is because I'll expand this with float and double vectors, and functions that convert between those vector types may need to include the internal headers for those types.)
The definition of the IVEC() macro uses the aforementioned ALT4() macro to pick the correct initialization macro. If only three arguments are specified, the r component is initialized to zero, and if only two, both r and z components are initialized to zero.
Since this file expects the compiler to provide the underlying vector (as in SIMD vector, not as in C++ vector) support, there is nothing unusual in the basic arithmetic operations.
In C++, ivec_mul() is an overloaded function, which calls either ivec_mul_ivec() or ivec_mul_i32(), depending on the type of the second parameter.
Similarly for ivec_div(), ivec_div_ivec(), and ivec_div_i32().
In C, ivec_mul() is a convenience macro based on the C11 _Generic() facility, expanding to ivec_mul_ivec() if the second parameter is an ivec, and to ivec_mul_i32() otherwise.
Similarly for ivec_div(), ivec_div_ivec(), and ivec_div_i32().
ivec_cross3() uses four temporary vector constants to reorder the components, so that the difference of the products yields the expected 3D vector cross product. This tends to let the compiler generate faster SIMD code, compared to just defining the result as an array with the component-wise formulae.
Here is a simple test program to visually verify the operations do work. (Note that this is not an unit test: for an unit test, I'd use a PRNG, most likely Xorshift64*, to generate random test values, and compare them to the results from naïve versions implemented in the unit test, written in as easy-to-read/verify form as possible.) test.c or test.cpp:
#include <stdlib.h>
#include <stdio.h>
#include "ivec.h"
static void describe_ivec(const char *description, const ivec v)
{
printf("%s = (%d, %d, %d)\n", description, (int)IVEC_X(v), (int)IVEC_Y(v), (int)IVEC_Z(v));
}
int main(void)
{
ivec a = IVEC(1, 2, 3);
ivec b = IVEC(4, 5, 6);
describe_ivec("a", a);
describe_ivec("b", b);
describe_ivec("ivec_add(a, b)", ivec_add(a, b));
describe_ivec("ivec_sub(a, b)", ivec_sub(a, b));
describe_ivec("ivec_mul(a, b)", ivec_mul(a, b));
describe_ivec("ivec_div(a, b)", ivec_div(a, b));
describe_ivec("ivec_mul(b, 3)", ivec_mul(a, 3));
describe_ivec("ivec_div(b, 2)", ivec_div(b, 2));
describe_ivec("ivec_cross3(a, b)", ivec_cross3(a, b));
printf("ivec_dot3(a, b) = %d\n", (int)ivec_dot(a, b));
return EXIT_SUCCESS;
}
You can compile the above (as either example.c or example.cpp) using gcc, g++, or clang; I haven't checked other compilers, but at least Intel compiler either works or can be made to work by adding a small snippet of code into ivec.h.
If you enable optimization (I tested with -O2), and examine the generated binary, you'll find that the example executable does not actually do any SIMD operations at all, and just prints preset constants! This is exactly as I wanted: a desired side effect of how the PURE_ACCESSOR -annotated helper functions should be implemented by the compiler. (But, if you generate one or both randomly at runtime or based on input, SIMD instructions will be used, of course.)
(Additional functions, like component-wise minimum and maximum, and Manhattan distance (sum of components magnitudes), could use <immintrin.h> on x86 and x86-64, or GCC x86/x86-64/ARM vector built-ins, and rely on the compiler to generate sane code from component-wise expressions on others. I omitted those to keep to some semblance of simplicity, but I'd probably put those alternatives into sub-header-files ivec_internal_generic.h, ivec_internal_sse3.h, ivec_internal_avx.h, and ivec_internal_neon.h, for example.)
If I add fvec (four-component float (32-bit FP) vectors), I'll add an fvec.h and fvec_internal.h.
Then, I'll also create an vecs.h that includes both ivec.h and fvec.h, and a new vecs_internal.h that defines the conversion functions between ivec and fvec types.
This way, files or headers that use either type (or both but not the conversion functions), just include "ivec.h" and/or "fvec.h"; and those that need the conversion functions too, include "vecs.h". Because the features are split into the internal parts, simple include guards as implemented above will take care of the order and re-inclusion absolutely fine. Even including all tree (ivec.h, fvec.h, vecs.h) is absolutely fine, and yields the same generated code as if one only included vecs.h.
In this scheme, therefore, #include "header" means "This particular source or header file requires the facilities declared by header".
When tracing the source code, it is common for the compilation unit to have multiple includes of the same header, just in different header files.
This sceme also plays perfectly well with dependency tracking. For example, if you use gcc , options -M -MF sourcefile.deps generates no object file, but saves the Makefile rule specifying the dependencies of the object file as file sourcefile.deps. These can then be include'd in the Makefile, so that changes (modification timestamps to later than the dependent object file) to any source or header files, including system header files, will cause (only) the affected object files to be recompiled. Usually, the dependency tracking target is called deps, so that if you create new files or delete old ones, or change any #include directives, you only need to run make deps to update the dependency information. It only takes a few lines in the Makefile to automate all this, but it depends very much on how you structure your source trees, so there is no one single recipe to fit everyone.