By Agustín Nieto García


2019-07-09 11:22:45 8 Comments

char* asctime (const struct tm * timeptr);
char* ctime (const time_t * timer);

I found that many functions inside of time.h return pointers to static variables, which could be altered by any subsequent call to those functions. That means I have to copy the data I just got as a result and it's an extra operation I have to execute and that makes those functions thread-unsafe.

Why was it implemented that way? Wouldn't these signatures be better?

void asctime (char * out, const struct tm * timeptr);
void ctime (char * out, const time_t * timer);

We always have to take decisions during development. I'm just asking why they chose to return a static pointer instead of taking an "out variable" as a parameter.

By the way (this is another question), why don't they allocate their result on the heap? Is it to allow the use of anything instead of malloc or just for efficiency?

4 comments

@Acorn 2019-07-09 11:31:21

That means I have to copy the data I just got as result

Why do you need to copy it?

Note that even if you copy the data as soon as you get it, you will still be open to races.

Why was it implemented that way?

Because when they were standardized (1989), most software wasn't multi-threaded even if multi-threading existed since the mainframe era. For reference, even POSIX threads were standardized years later (1996) than these functions. It did not help either that computers got quicker every single year and multi-core/SMT processors did not appear until 2001-2006.

If you needed something else, you could always use a system-specific function.

why don't they allocate their result on the heap?

Allocating is very expensive.

Is it to allow the use of anything instead of malloc or just for efficiency?

Not sure what you mean by that. The proper way to do this is to pass a pointer to the destination buffer, so that the user chooses what allocation method to use.

@Andrew Henle 2019-07-09 12:22:00

Multi-core systems appeared in relatively widespread commercial use about 1992. ANSI C was de facto documenting existing codebases which were designed with only single threads of execution in mind. There's also significant pushback against standardizing functions that allocate memory in C itself - note the lack of strdup() in the C standard.

@Acorn 2019-07-09 13:01:24

@AndrewHenle Wasn't sun4d multi-CPU rather than multi-core/SMT? I thought Sun didn't get into multi-core until the UltraSPARC IV (2004). Note that multi-CPU computers/mainframes have existed since the 60s/70s; but while you can do multi-threading in multi-CPU systems, it was rare as you say (multi-processing was way more common AFAIK, you didn't need to exploit the benefits of the shared L2/3 caches).

@Acorn 2019-07-10 07:36:40

@Luaan Memory being shared between all the CPUs just makes it SMP, not SMT.

@Luaan 2019-07-10 08:20:20

@Acorn Duh, my bad. I assumed you meant SMP, since SMT shouldn't have any effect on code running on the processor (beyond timing). The main point is that on the hardware level, the sun4d didn't isolate the processors - so you could have software that was fully multi-threaded. I have no idea if the OS/software had any support for multi-threading or if it was exclusively used for multi-processing.

@Acorn 2019-07-10 10:34:13

@Luaan There were SMP systems which supported multi-threading many years before the sun4d, like all the mainframes in the 70s with UNIX etc. However, I would imagine there was not much incentive making software multi-threaded. I will edit the answer, nevertheless, to make it more clear.

@dbush 2019-07-09 11:30:34

The specification of the ctime and asctime functions goes back to C89, and things were done a bit different back in those days, mainly because multi-processor systems weren't very common and thus using a static buffer wouldn't cause a big problem.

Most likely, they didn't return dynamically allocated memory because it took extra time, and in those days CPU cycles were harder to come by.

If you're on a POSIX system like Linux, you have two other functions available which are basically what you described as an alternative:

   char *asctime_r(const struct tm *tm, char *buf);
   char *ctime_r(const time_t *timep, char *buf);

These function take a pointer to a buffer that can receive the output (and they return a pointer to that same buffer). The _r suffix means "reentrant", meaning it can safely be called either in a multithreaded program or more than once without a sequence point in between.

@Mark 2019-07-09 20:37:10

The other advantage to returning a pointer to a static buffer is that it's impossible to leak the memory, so you can do things like printf("It is now %s\n", ctime(time(NULL))).

@Guntram Blohm 2019-07-10 07:50:51

And the functions know the maximum needed buffer size, so overflowing the buffer isn't possible either.

@John Bode 2019-07-09 14:45:12

C is a product of the early 1970s, and that legacy shows in things like this. strtok also uses a static buffer and is neither thread-safe nor re-entrant.

I've not seen a definitive explanation for why those functions were implemented that way. It may have been to save stack space (128 kB was a lot of very expensive memory at the time), it may have been to avoid runtime checks on the size or validity of the target buffer, etc. C was originally designed for systems programming, so if time calculations were being done a lot, I can see this approach saving a significant number of cycles over the course of the day.

Unfortunately, that's speculation on my part. I agree that passing the target buffer is the better solution, and that should be the path forward.

@Caleth 2019-07-09 11:31:49

You are (almost) describing the _s variants that were added in C11

errno_t ctime_s(char *buffer, rsize_t bufsz, const time_t *time);
errno_t asctime_s(char *buf, rsize_t bufsz, const struct tm *time_ptr);

These write to the specified location, provided it is big enough, and report the error otherwise.

You don't need to malloc the buffers for these calls, as you know char buf[26]; is exactly what is needed.

@John Bollinger 2019-07-09 11:48:03

... that were added as an optional feature in C11. In practice, few compilers implement these or the other Annex K functions. Even Microsoft, the source of that whole boondoggle, doesn't implement the *_s functions fully according to the specifications that were accepted into the language standard.

@Andrew Henle 2019-07-09 12:33:30

@JohnBollinger Even Microsoft, the source of that whole boondoggle, doesn't implement the *_s functions fully according to the specifications that were accepted into the language standard. It's worse than that. Microsoft's implementation doesn't even conform with the original TR 24731-1 technical report itself, which I presume originated with significant Microsoft input.

Related Questions

Sponsored Content

17 Answered Questions

[SOLVED] What REALLY happens when you don't free after malloc?

  • 2009-03-17 15:29:09
  • Scott
  • 101224 View
  • 494 Score
  • 17 Answer
  • Tags:   c malloc free

6 Answered Questions

7 Answered Questions

3 Answered Questions

[SOLVED] Why do arrays in C decay to pointers?

  • 2015-10-22 22:09:41
  • Peter A. Schneider
  • 2748 View
  • 19 Score
  • 3 Answer
  • Tags:   c arrays pointers

5 Answered Questions

[SOLVED] Why 2 stars when passing pointer to a string to a function

  • 2014-12-17 21:07:52
  • Me_me_me
  • 2290 View
  • 3 Score
  • 5 Answer
  • Tags:   c pointers memory

2 Answered Questions

[SOLVED] asctime() weird behaviour

  • 2014-08-21 09:44:22
  • Andrew
  • 157 View
  • 0 Score
  • 2 Answer
  • Tags:   c time.h

2 Answered Questions

Using static instead of malloc - C-language

3 Answered Questions

[SOLVED] Where are Parameter variables stored in memory?

  • 2013-04-30 16:27:10
  • James Carter
  • 10548 View
  • 2 Score
  • 3 Answer
  • Tags:   c variables

2 Answered Questions

7 Answered Questions

[SOLVED] Returning pointers from the class. Who is responsible for delete?

  • 2012-03-01 14:52:28
  • Slava
  • 289 View
  • 4 Score
  • 7 Answer
  • Tags:   c++ c abi

Sponsored Content