20

According to the documentation the struct tm *gmtime(const time_t *timer); is supposed to convert the time_t pointed to by timer to a broken down time.

Now is there a reason why they decided to make the function take a pointer to the time_t instead of passing the time_t directly?

As far as I can see time_t is of arithmetic type and should therefore have been possible to pass directly (also I find it reasonable that it would have fit into a long). Also there seem to be any specific handling of the NULL pointer (which could have motivated passing a pointer).

Is there something I'm missing? Something still relevant today?

skyking
  • 13,817
  • 1
  • 35
  • 57
  • At a guess I'd say at some point they envisioned that `time_t` might be a `struct` and wanted to efficiently handle that scenario if it ever happened. – Sean Apr 25 '16 at 10:13
  • @Sean but question remains, why pointer? – Sourav Ghosh Apr 25 '16 at 10:17
  • @Sean, here is proto from UNIXv7: `struct tm * gmtime(tim) long *tim;`. No structs here. – myaut Apr 25 '16 at 10:18
  • 4
    Structs that are referenced via pointers can be extended while remaining backwards-compatible. – Martin James Apr 25 '16 at 10:25
  • Note that both `clock_t` and `time_t` cannot be structures. Some standard functions even return `(time_t)-1` in some cases. – user694733 Apr 25 '16 at 10:34
  • @Sean For that explaination to work you would have to had a standard not requiring `time_t` to be of arithmetic type... – skyking Apr 25 '16 at 10:38
  • @skyking - my point is that although it's a standard now there may have been a point before standardization where they were hedging their bets... – Sean Apr 25 '16 at 10:40
  • See http://www.catb.org/esr/time-programming/ for some historical information. – Steve Summit Apr 25 '16 at 10:49
  • 1
    @myaut look at UNIX v6 - there it's `gmtime(tim) int tim[];` (it returned a pointer to a 9-element int array - there were structs, but no struct tm) – Random832 Apr 25 '16 at 16:53

2 Answers2

14

From what I've seen, it's more a historical quirk. When time.h was first introduced, and with it functions like time, it used a value that could not be returned (ie: no long int etc). The standard defined an obscure time_t type that still leaves a lot of room for vendors to implement in weird ways (it has to be an arithmetic type, but no ranges or max values are defined for example) - C11 standard:

7.27.1 Components of time.
[...]
3 .The types declared are size_t (described in 7.19);
clock_t
and
time_t
which are real types capable of representing times;
4. The range and precision of times representable in clock_t and time_t are implementation-defined.

size_t is described, in C11, as "is the unsigned integer type of the result of the sizeof operator;"

In light of this, your comment ("I find it reasonable that it would have fit into a long") is an understandable one, but it's incorrect, or at the very least inaccurate. POSIX, for example requires time_t to be an integer or real floating type. A long fits that description, but so would a long double which doesn't fit in a long. A more accurate assumption to make would be that the minimum size of time_tis a 32bit int (until 2038 at least), but that time_t preferably is a 64bit type.

Anyway, back in those days, if a value couldn't be returned, the only alternative was to pass the memory to the function (which is a sensible thing to do).
This is why we have functions like

time_t time(time_t *t);

It doesn't really make sense to set the same value twice: once by returning it, and once using indirection, but the argument is there because originally, the function was defined as

time(time_t *t)

Note the lack of a return type, if time were added today, it'd either be defined as void time( time_t * ), or if the committee hadn't been drinking, and realized the absurdity of passing a pointer here, they'd define it as time_t time ( void );

Looking at the C11 standard regarding the time function, it seems that the emphasis of the functions' behaviour is on the return value. The pointer argument is mentioned briefly but it certainly isn't given any significance:

7.27.2.4 The time function

1. Synopsis

#include <time.h>
time_t time(time_t *timer);

2. Description

The time function determines the current calendar time. The encoding of the value is unspecified.

3. Returns

The time function returns the implementation’s best approximation to the current calendar time. The value (time_t)(-1) is returned if the calendar time is not available. If timer is not a null pointer, the return value is also assigned to the object it points to.

The main thing we can take from this is that, as far as the standard goes, the pointer is just a secondary way of getting at the return value of the function. Given that the return value indicates something went wrong ((time_t)(-1)), I'd argue that we should treat this function as though it was meant to be time_t time( void ).

But because the old implementation is still kicking around, and we've all gotten used to it, it's one of those things that should've been marked for deprecation, but because it never really was, it probably will be part of the C language for ever...

The only other reason why functions use time_t like this (const) is either historical, or to maintain a consistent API across the time.h API. AFAIK that is

TL;DR

Most time.h function use pointers to the time_t type for historical, compatibility, and consistency reasons.


I knew I read that stuff about the early days of the time function before, here's a related SO answer

Community
  • 1
  • 1
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • This is most likely explanation. Combination of hardware limitations vs performance, backwards compatibility, and *"it seemed like a good idea at the time"*. – user694733 Apr 25 '16 at 10:42
  • So the answer is "for historical, and consistency reasons". I don't consider that an answer. – trojanfoe Apr 25 '16 at 10:44
  • 3
    @trojanfoe So say "for historical and backwards-compatibility reasons". See http://www.catb.org/esr/time-programming/ for more info. – Steve Summit Apr 25 '16 at 10:50
  • Well that would make more sense. – trojanfoe Apr 25 '16 at 10:52
  • Do you have any reference or quotes from the relevant standards? – skyking Apr 25 '16 at 13:15
  • @skyking: added some relevant parts of the standard (C11) to the answer – Elias Van Ootegem Apr 25 '16 at 15:45
  • @EliasVanOotegem Those references requires `time_t` to basically be integer or floating point type - if that's the case you should be able to just pass the value. The question is more of if there's a specification where this was not possible (and preferably an implementation where `time_t` had a type that couldn't be passed as a value) – skyking Apr 26 '16 at 05:16
0

The reason is probably that in the old days a parameter could be no larger than an integer, so you couldn't pass a long and had to do that as a pointer. The definition of the function never changed.

Paul Ogilvie
  • 25,048
  • 4
  • 23
  • 41
  • `time_t` is an integer. I think it's just a "bug" as there is no good reason. – trojanfoe Apr 25 '16 at 10:36
  • @trojanfoe, on my VC2008 `time_t` it is 64 bit! – Paul Ogilvie Apr 25 '16 at 10:40
  • Integers can be 64-bit. – trojanfoe Apr 25 '16 at 10:40
  • 1
    @trojanfoe: There's nothing in the standard that ensures `time_t` to be an integer. It's an arithmetic type, but not an int (it needn't be at least: POSIX allows both integer types and floating point types to be used, provided the underlying type is _either_ 32 or 64 bit – Elias Van Ootegem Apr 25 '16 at 10:41
  • `time_t` is only required to be of arithmetic type. I interpret that as it is allowed for it to be `double` for example... – skyking Apr 25 '16 at 10:41
  • @EliasVanOotegem I never said it had to be an integer. I was arguing with the statement in this answer "in the old days a parameter could be no larger than an integer" by saying that `time_t` was an integer (in every implementation I've seen). This answer makes no sense, but then nor does yours. – trojanfoe Apr 25 '16 at 10:43
  • @trojanfoe, my VC2008 compiles for 32 bit, so even though a 64 bit thing can be an integer, it is not a native integer. Maybe I should have said "native integer" – Paul Ogilvie Apr 25 '16 at 10:43
  • That doesn't make sense either. C has managed to cope with longer-than-native-sized-integers since the start. – trojanfoe Apr 25 '16 at 10:45
  • @trojanfoe It looks like a "bug" today, but this is basically the answer. In the very early days of C, type `int` was 16 bits, and type `long int` had not been invented yet. (Forget about 64-bit `long long`.) And when 32-bit `long` was first introduced, there was initially some reluctance to pass and return them directly. This also explains the 'l' in `lseek`. – Steve Summit Apr 25 '16 at 10:47
  • 1
    @trojanfoe, No it didn't. Only with compiler support to insert code e.g. to put two native words together and handle it as a larger thing. Think of overflow of the low word not automatically incrementing the high word. You must go/think back in time when C was merely an assembler generator. – Paul Ogilvie Apr 25 '16 at 10:49
  • So you couldn't pass `time_t` as a parameter but you could return it? How does that work? – trojanfoe Apr 25 '16 at 10:49
  • @trojanfoe, as Elias Van Ootegem explains, the original definition was `time(time_t *t)` (`void` not being defined yet, but intended here) – Paul Ogilvie Apr 25 '16 at 10:50
  • 1
    @trojanfoe First, it wasn't called `time_t` then, it was just `long`. Second, it's easier to push two words on the stack than it is to return two words in a machine register that only holds one. – Steve Summit Apr 25 '16 at 10:52
  • 1
    @trojanfoe "C has managed to cope with longer-than-native-sized-integers since the start" Not true. Very early C had 16-bit `int` and no `long`. – Steve Summit Apr 25 '16 at 10:54
  • @SteveSummit Was that even guaranteed? Wasn't it something like `int` being a native integer machine word for the machine architecture in question, and then `long` being at least as long as a native integer machine word? That `int` happened to become 16 bits was a consequence, not really an intentional choice. It also goes better with C's history of being a portable form of assembler. – user Apr 25 '16 at 12:34
  • Disagree with "... in the old days a parameter could be no larger than an integer," as `` has many examples of a `double` argument and `double` is commonly` larger than an `int`. – chux - Reinstate Monica Apr 25 '16 at 14:30
  • @MichaelKjörling You're right, the definition of `int` has always been, right back to the beginning, "the native word size of the machine". (But of course the PDP-11 was a 16-bit machine, with only partial support for 32-bit longwords, thus some of the odd choices made for syscalls like `time` and `lseek`.) – Steve Summit Apr 25 '16 at 14:43
  • @chux, in the old days the _compiler_ supported `double` but the _machine_ did not. That means the compiler inserted instructions to handle doubles and inserted library calls to perform calculations on doubles. It could for example turn every double argument into the push of 4 native words and handle the return of such a type as a pointer to compiler-allocated stack space. It was compiler dependent and every compiler could (and did) have its own way. – Paul Ogilvie Apr 25 '16 at 15:16