The QueueUserWorkItem
function schedules a function to be called from a thread pool thread. It’s one of the so-called legacy thread pool functions, a group of functions introduced in Windows 2000 in the first version of the system thread pool. It has since been superseded by the new thread pool functions like CreateThreadpoolWork
, but the old functions continue to work, for compatibility. (They are implemented as wrappers around the new thread pool functions.)
Curiously, the QueueUserWorkItem
function takes a LPTHREAD_
function pointer as the function to run on a thread pool thread. This is curious because the LPTHREAD_
function returns a DWORD
. What does the system do with the DWORD
returned by a work item?
Nothing.
The return value is simply discarded.
It doesn’t matter what you return, as long as you return something. (And do make sure you return something. Don’t just fall off the end of the function and return uninitialized garbage, because that uninitialized garbage could be deadly.)
Mind boggles why the garbage is deadly. When ANSI C was new, it allowed falling off the end of an int function so long as the caller didn’t check. Even itamium only faulted when you wrote the value to memory, which you won’t because it’s call-clobbered.
If anything, we’re all spoiled these days because the compiler will generally just go ahead and insert a “return 0;” if you just fall off the end of an int-defined function. You have to work pretty hard to corrupt the stack on accident now.
I thought modern compilers are more aggressive in exploiting undefined behaviors?
… the compiler will generally just go ahead and insert a “return 0;” if you just fall off the end of an int-defined function.
If I'm not mistaken, your statement is only applicable to the main function.
Practically speaking it's limited how undefined it can get because the compiler can't see the call site in this case. It needs to emit the function as if it was going to be called with the signature you claimed. The cast itself is valid as long as its cast back to a function pointer compatible with the signature, and since this is happening in an external dll the compiler won't know that there is any...
Compatible type is defined in C99 §6.2.7, with references to 6.7.2, 6.7.3, and 6.7.5. It basically means the same type, but with a lot of additional pedantic rules (e.g. enumerations are compatible with an implementation-defined integer type).
A pointer to a function of one type may be converted to a pointer to a function of another type and back again……
which is not what we're doing here.
If a converted pointer is used to call a function whose type is not compatible with the pointed-to type……
I'm not sure what "compatible" means here; it could mean ABI-compatible or type-identical or something else. Unfortunately I couldn't easily find an answer to it on...
You're right that at a specific ABI boundary there is no notion of "undefined behavior"; everything is defined by registers and memory.
However, it doesn't apply to the case of undefinedness that is already on the language level. For example, inside an int-returning function the compiler is free to assume that the closing curly brace is never reached (since that would be UB) and thus for
<code>
the compiler may choose not to generate even...
> On the topic of function pointer casting then, observe that it’s still language-level UB since the correct signature of the function has been declared in the translation unit, and the cast into a contradictory function pointer type is expressed in language-level expressions.
Casting between incompatible function pointers isn't UB in C, it is only UB when the function is called through an incompatible function pointer type.
From ISO/IEC 9899:2018 Section 6.3.2.3, paragraph 8:
> A pointer to...