Visual C++ Incompatible time_t Types
Submitted by craiga on Tue, 01/01/2008 - 19:47.
One of the main reasons for using the correct named types in C programs is to ensure that, for a particular compiler on a particular architecture, the type is as expected at all times. One of these is time_t, for representing timestamps. In most compilers it is a 32 bit integer value, but you cannot just blindly assume that time_t can be cast to and from int, or long, or whatever, at will.
There is an issue with closed source, binary only libraries. What if you are linking them against a different compiler to the one they were built with, and what if it has a different idea of what different types actually are? Well, bad things can happen. In Visual C++ 2005, time_t is defined as a 64 bit integer. In all previous versions it was 32 bit. To demonstrate what can happen, consider this library, built with VC++ 2003, that exposes the following function:
void populate_user(int user_id, user_t* user);
The type user_t is defined as a structure in the distributed header file like this:
typedef struct _user_t {
char username[8];
time_t creation_time;
time_t lastlogin_time;
char realname[64];
} user_t;
It's a fairly simple struct, but it'll do to demo the problem. So we build a new program that links against our library and #includes our header. It creates a new variable on the stack of type user_t and passes a pointer to it into the exported function.
So what happens? Well, the following values are inserted at the given offsets:
| Member | Offset | Value | Memory Contents |
|---|---|---|---|
| username | 0 | craiga | 72 63 69 61 61 67 00 … |
| creation_time | 8 | 1199215958 | 47 7a 95 56 |
| lastlogin_time | 12 | 1199216158 | 47 7a 96 1e |
| realname | 16 | Craig Andrews | 43 72 61 69 67 20 41 6e 64 72 65 77 73 00 … |
However, when that little lump of memory makes it into our VC++ compiled program with its 64 bit time types, this is what it looks like:
| Member | Offset | Value | Memory Contents |
|---|---|---|---|
| username | 0 | craiga | 72 63 69 61 61 67 00 … |
| creation_time | 8 | 5150594180643984726 | 47 7a 95 56 47 7a 96 1e |
| lastlogin_time | 16 | 7944666846179979843 | 43 72 61 69 67 20 41 6e |
| realname | 24 | drews | 64 72 65 77 73 00 … |
What happened?! Well, all the offsets of the members are wrong. The two 32 bit integer values in the library have been crammed into one 64 bit field in the program. Then, the contents of the realname string have been used to populate the time values. The end result is that the realname string is offset by 8 characters and the two times are completely garbled.
Unfortunately, unless the vendor has provided a workaround, you are pretty much stuck with it. Fortunately for us, Microsoft have actually given us a sort-of fix for this issue. Simply include the definition of _USE_32BIT_TIME_T in your project's (or file's) Preprocessor Definitions field and you're away. Unfortunately it means that anything linking against your older binary will be stuck using 32 bit time_t, even if you want to use 64bit time_t. Not a great solution, then, but better than the alternative!
