Date: Wed, 24 Sep 2025 07:39:27 -0700
From: Paul Eggert <[email protected]>
Message-ID: <[email protected]>
| On 2025-09-24 00:36, Robert Elz wrote:
| > If what is there is the same as in C89
|
| It's not. C89 �4.12.3 "Time conversion functions" says, "Except for the
| strftime function, these functions return values in one of two static
| objects: a broken-down time structure and an array of char. Execution of
| any of the functions may overwrite the information returned in either of
| these objects by any of the other functions."
That is actually more reasonable (more accurate to what is permitted)
in that it allows ctime() to alter the struct tm that localtime() returns,
but apart from that it is almost just the same.
| And �4.12.3.2 says that ctime(x) is equivalent to
| asctime(localtime(timer)).
Yes, that's always been there, and says what the effect is, but that
kind of thing doesn't require the implementation to be that. Once
again, allows it, does not require it.
| This wording is
| intended to require the 7th Edition Unix behavior, where calling any of
| localtime, gmtime, and ctime overwrote the same struct tm object.
No it isn't, it is meant to allow that behaviour - no-one with even an
iota of sanity would require things to be implemented that way.
| So in C89 it's crystal clear that successful calls to localtime and
| gmtime must return the same pointer.
It doesn't say that at all, it still says "may overwrite", you're much too
hung up on the "two objects" part.
| C23 makes it clear that the requirement is no longer present;
What they did there (which is the obvious change to make) is to more
reasonably define a sane lifetime for the static data that is being
returned, to avoid the issue that your gmt3.c code encountered (not that
I can imagine any real code ever acting like that).
If they were making a fundamental change to the way that these functions
were required to behave, the way you are imagining they did, there would
have been much more noise about this - that would have been a seriously
major change. What's more, if the requirement was as you imagine it to
have been, the change that they made would have not been needed, as there
could never have been other than one static struct tm, global, and its
lifetime would never have been in doubt.
| C99 through C17 (and therefore
| POSIX.1-2001 through POSIX.1-2024) relax the C89 requirement to allow
| localtime and gmtime to use different objects,
Even C89 permitted that.
| But even under your more relaxed interpretation, the FreeBSD behavior
| does not conform to POSIX versions through POSIX.1-2024, or to C89
| through C17, because it sometimes frees storage addressed by the
| returned values of localtime and gmtime, something these standards do
| not allow.
That I agree with, which is why I did not object to your gmt3.c test,
which exposes that issue. When you asked:
| Changed to what, though?
in response to a suggestion that the standard should be altered, I was
(before you pointed out the C23 change) considering suggesting something
essentially equivalent to that change - it is the obvious, and sane, thing
to do.
| Any library that wants to support old binaries that assume
| pre-POSIX.1-2001 behavior must have just one object for both gmtime and
| localtime.
Nonsense.
| This 7th Edition Unix behavior conforms to all the standards,
It does.
| and portable programs should allow (though not insist on) this behavior.
Exactly. The only reason for having the library act the way you say it
is required to act, is to support imaginary applications which might assume
this is required. I've never seen one, the typical application issue is
in assuming that the results aren't modified, I've never seen one that
insists that they must be. Never. So, requiring the library (the
implementation) to act like that, so those imaginary applications keep on
working (like your gmt2.c - the only code I have ever seen which tests that)
is just too much. How much effort the implementation should take to
allow broken code to work is up to the implementor - but the FreeBSD
compromise seems about right to me.
In yet another message (the one that attached gmt2.c) you noted:
| I see that a single-threaded program does not have this issue: gmtime and
| localtime return the same pointer in single-threaded programs. So if the
| motivation is to cater to poorly-written unportable programs, it appears
| that this motivation doesn't extend to the common mistake of thinking that
| gmtime and localtime return different pointers.
which is correct - the difference between the two is that the non handled
issue is trivial for an application to fix
struct tm tm1, tm2;
tm1 = *localtime(&t);
tm2 = *gmtime(&t);
(ideally with a little error checking for a NULL return included). The
lifetimes of tm1 and tm2 can be whatever the application needs, and nothing
will just randomly alter those.
But there's no trivial technique like that which will handle multiple threads
doing calls like these, and apart from using different objects in different
threads, there's nothing that the implementation can possibly do which can
deal with this problem. An application which knows it is threaded can deal
with this issue (simply call the _r functions instead), but should that
application need to make use of a library which simply calls the original
(old functions) when it needs to, there is no solution other than implementing
locks around every call to any function in that library to prevent 2
threads from ever calling functions in that library concurrently - and that's
even if the library has written the code as above, believing that would
avoid issues with static state, and that it should work - but it just doesn't.
kre