Is &errno legal C?

后端 未结 5 989
南方客
南方客 2020-12-23 19:43

Per 7.5,

[errno] expands to a modifiable lvalue175) that has type int, the value of which is set to a positive error number by several library functio

5条回答
  •  刺人心
    刺人心 (楼主)
    2020-12-23 20:06

    The original implementation of errno was as a global int variable that various Standard C Library components used to indicate an error value if they ran into an error. However even in those days one had to be careful about reentrant code or with library function calls that could set errno to a different value as you were handling an error. Normally one would save the value in a temporary variable if the error code was needed for any length of time due to the possibility of some other function or piece of code setting the value of errno either explicitly or through a library function call.

    So with this original implementation of a global int, using the address of operator and depending on the address to remain constant was pretty much built into the fabric of the library.

    However with multi-threading, there was no longer a single global because having a single global was not thread safe. So the idea of having thread local storage perhaps using a function that returns a pointer to an allocated area. So you might see a construct something like the following entirely imaginary example:

    #define errno (*myErrno())
    
    typedef struct {
        // various memory areas for thread local stuff
        int  myErrNo;
        // more memory areas for thread local stuff
    } ThreadLocalData;
    
    ThreadLocalData *getMyThreadData () {
        ThreadLocalData *pThreadData = 0;   // placeholder for the real thing
        // locate the thread local data for the current thread through some means
        // then return a pointer to this thread's local data for the C run time
        return pThreadData;
    }
    
    int *myErrno () {
        return &(getMyThreadData()->myErrNo);
    }
    

    Then errno would be used as if it were a single global rather than a thread safe int variable by errno = 0; or checking it like if (errno == 22) { // handle the error and even something like int *pErrno = &errno;. This all works because in the end the thread local data area is allocated and stays put and is not moving around and the macro definition which makes errno look like an extern int hides the plumbing of its actual implementation.

    The one thing that we do not want is to have the address of errno suddenly shift between time slices of a thread with some kind of a dynamic allocate, clone, delete sequence while we are accessing the value. When your time slice is up, it is up and unless you have some kind of synchronization involved or some way to keep the CPU after your time slice expires, having the thread local area move about seems a very dicey proposition to me.

    This in turn implies that you can depend on the address of operator giving you a constant value for a particular thread though the constant value will differ between threads. I can well see the library using the address of errno in order to reduce the overhead of doing some kind of thread local lookup every time a library function is called.

    Having the address of errno as constant within a thread also provides backwards compatibility with older source code which used the errno.h include file as they should have done (see this man page from linux for errno which explicitly warns to not use extern int errno; as was common in the old days).

    The way I read the standard is to allow for this kind of thread local storage while maintaining the semantics and syntax similar to the old extern int errno; when errno is used and allowing the old usage as well for some kind of cross compiler for an embedded device that does not support multi-threading. However the syntax may be similar due to the use of a macro definition so the old style short cut declaration should not be used because that declaration is not what the actual errno really is.

提交回复
热议问题