Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Comments on noreturn function with non-void return type

Parent

noreturn function with non-void return type

+5
−0

Is it legal ISO C to declare a function as noreturn with a non-void return type (but of course not actually returning)?

As far as I can read from the standard, it seems legal.

Example:

noreturn void *foo(void *x)
{
	pthread_exit(x);
}

ISO C (N2731, C2x) says:

6.7.4 Function specifiers

8 A function declared with a _Noreturn function specifier shall not return to its caller.

Recommended practice

9 The implementation should produce a diagnostic message for a function declared with a _Noreturn function specifier that appears to be capable of returning to its caller.

12 EXAMPLE 2

_Noreturn void f () {
    abort(); // ok
}
_Noreturn void g (int i) { // causes undefined behavior if i <= 0
    if (i > 0) abort();
}

J.2 Undefined behavior

  • A function declared with a _Noreturn function specifier returns to its caller (6.7.4)

There's some doubt in 6.7.4/9, where it says that it's recommended to diagnostic a function that appears to be capable of returning to its caller (noreturn void *foo(void *x), from seeing only it's prototype, "appears to be capable of returning to its caller"), but the other text (and the example) seems to allow it as long as the function body doesn't seem to return.

If it's valid, it can be useful to pass &foo to pthread_create(3), which expects a function that returns void *, while marking it as noreturn, since it won't return.

pthread_create(3):

       #include <pthread.h>

       int pthread_create(pthread_t *restrict thread,
                          const pthread_attr_t *restrict attr,
                          void *(*start_routine)(void *),
                          void *restrict arg);

Current GCC and Clang seem to not warn about it, with -Wall -Wextra (and Clang -Weverything), so it looks good.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

Post
+3
−0

Syntax-wise it is a function specifier and may in theory appear everywhere where inline (or rather the syntax item function-specifier:) can appear, since the standard doesn't say otherwise. Though of course it would be nonsense to declare _Noreturn together with a return type and a compiler failing to warn against such would be a low quality of implementation.

Though I believe the "recommended practice" part is a "code coverage" recommendation, as in having the compiler diagnose if any execution path inside the function could lead to it returning, rather than just checking if it is declared with a return type other than void. This is just a recommendation though, so it isn't normative or required. The only normative text here is 6.7.4/8 and violating it would lead to undefined behavior.

Regarding pthread callbacks I'm not sure what benefit it would yield other than the compiler perhaps skipping some return instruction and saving a few bytes of code size. Arguably, a thread function which never returns but has to be clobbered to death with brute force is incorrectly designed - threads should always be able to exist gracefully on their own upon the reception of a event/semaphore etc. Never returning would potentially also break pthread_join, in case it expects a certain calling convention but the callback deviates from the expected void* f(void*) format (by not stacking the return pointer etc).

As for Clang specifically, I very much doubt it is capable of giving warnings about _Noreturn. Clang is as far as I know still completely broken in this regard and unable to follow the 6.7.4 recommendation since it is unable to correctly generate execution paths to begin with, let alone diagnose them. See this compiler bug: How do I make an infinite empty loop that won't be optimized away?

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.

2 comment threads

pthread_join(3) (3 comments)
It's quite sensible to have _Noreturn with a non-void return type (9 comments)
It's quite sensible to have _Noreturn with a non-void return type
Derek Elkins‭ wrote almost 3 years ago · edited almost 3 years ago

From a (type) theoretical perspective, not only is it sensible but it's quite natural to have non-returning functions of any type. Type theoretically, a type with no values is usually called Void (not to be confused with C's void which, type theoretically, is usually called "unit" and has exactly one value). A function returning Void can never return because there's literally nothing that can be returned. Part of the definition of Void is a method to consume it, often called absurd. absurd : Void -> a for any type a. This makes sense because absurd can "return" anything at all since it can never actually be called. If you wanted to reflect the fact that a function doesn't return in its type, you'd give it Void as it's return type (and exactly this happens in many languages with such a type). I don't see any reason theoretically or practically why the non-unit return type case should be warned against. It's certainly not nonsense.

Lundin‭ wrote almost 3 years ago

Derek Elkins‭ The whole practical rationale for this is cases like for example a "bare metal" system where your program starts up in a power-on reset interrupt. From there on it calls the C run-time start-up code never to return, so no need to waste stack on storing return address etc. Then the C run-time eventually calls main() never to return. Which in turn could call user functions that for example hang up in an eternal loop waiting for the watchdog to reset the CPU. It's senseless both to allocate stack memory as per calling convention for all these steps, as well as it is senseless to specify a return type of any of the functions along the way. Which is exactly why bare metal systems use implementation-defined void main (void) instead of strictly conforming int main (void).

Derek Elkins‭ wrote almost 3 years ago

I don't see what your getting at. The fact that there are cases where you want a void returning _Noreturn function doesn't mean there aren't cases where you want a non-void returning _Noreturn function. Where non-void returning _Noreturn functions would most obviously be useful is for function pointers which, unsurprisingly, is exactly the case in the question. I will say that given C's weak abilities to specify interfaces supporting multiple implementations, it's fairly rare that you're in a situation where the type of a function is fixed and your code must conform to it. If you are free to choose the return type and you know the function (by specification) won't return, then a void return type makes the most sense. When you aren't free to choose, well, then you simply don't have a choice.

alx‭ wrote almost 3 years ago · edited almost 3 years ago

Lundin‭ In this case, noreturn doesn't help the compiler optimize, because it can't optimize (and in fact it would be wrong to optimize, since pthread_create(3) expects a function that actually returns, and providing something different (void) might be UB (but it happens to be compatible by the ABI in this case, AFAIK). However, it helps the programmer reading the function, which will know that it never returns, and that is a good thing to annotate. In a similar fashion, I used noreturn for main() a few years ago in an embedded system, where it would never return, but I needed to use the standard prototype, so I used noreturn int main(void). That's an almost mirroring case to the one we have here now.

Lundin‭ wrote almost 3 years ago

Derek Elkins‭ Again, you can't do that for the same reasons as in my previous comment - calling convention. If you specify a return type but don't return, you risk tricking the compiler into thinking it should push/pop items on the stack which are never pushed/popped there by the function. This is also the main reason why C doesn't allow (implicit) function pointer conversions. You can't compare C to more high-level languages in this regard because C is very close to the actual machine code. Complaining about C's type system is quite similar to complaining about assembler's type system.

Lundin‭ wrote almost 3 years ago

alx‭ I wouldn't dare say anything about ABI compatibility between all *nix systems that have implemented pthreads over the years. Just take x86_32 Linux vs x86_64 Linux as the most obvious example.

Lundin‭ wrote almost 3 years ago

As a side note regarding embedded systems and main(), there's an issue where C++ claims that if main() is defined, it must be declared as int main() and I don't even think they made an exception for freestanding systems. In that case noreturn might save the day. That doesn't apply to C however, where freestanding systems may use implementation-defined forms of main(). Using C++ for embedded systems isn't something I'd recommend in general.

Derek Elkins‭ wrote almost 3 years ago

C is not "very close to actual machine code". (This is obviously true because C is used on a huge variety of machines, but even for mainstream CPUs it's become ever more divergent from what's actually happening.) The notion of a "stack" is not a concept in C, which is good since C is deployed on CPUs with no stack. You can say C is a purposely loosely specified language with few guarantees. Pushing something that isn't popped is a minor memory leak and would happen regardless of whether _Noreturn is specified. Popping something that wasn't pushed presumably will lead to some misbehavior. The C spec would need to allow this via undefined behavior, otherwise it would be a compiler bug. Does the spec say that making/using a function pointer to a _Noreturn function with non-void return type is undefined behavior? If so, that's the relevant piece, not comments about calling conventions.

Derek Elkins‭ wrote almost 3 years ago

Separately, I didn't complain about C's type system. If anything, the allowance of non-void _Noreturn functions is nicely in line with theory. At worst, I implied that void could be better named. I certainly could complain about aspects of C's type system and design in general. I don't really fault the creators of C for not making all the best design decisions when, at the time, it was pretty unclear what those were. I do fault newer languages that replicate C's mistakes when we definitely do know better (and they are subject to totally different constraints than C).