2

I am learning C++ using the books listed here. In particular, I read that flowing off the end of a non-void function is undefined behavior. Then I looked at this answer that says:

In C++ just flowing off the end of a value returning function is always undefined behavior (regardless of whether the function's result is used by the calling code). In C this causes undefined behavior only if the calling code tries to use the returned value.

But in this answer I read:

It is legal under C/C++ to not return from a function that claims to return something.

As you can see in the first quoted answer, the user says that in C++ it is always UB but the second quoted answer says that it is legal. They seem to be contradicting each other.

Which of the above quoted answer is correct in C++?

Also I have the following example in C++:

int func(int a, int b)
{
    if(a > b)
    {
        return a;
    }
    else if(a < b)
    {
        return b;
    }
}

int main()
{
    int x =0, y =0;
    std::cin>> x >> y;
    
    
    
    
    if(x!=y)
    {
        func(x,y); //Question 1: IS THIS UB?
        std::cout<<"max is: "<<func(x,y); //Question 2: IS THIS UB?
    }
    else 
    {
        std::cout<<"both are equal"<<std::endl;
    }
    return 0;
}

I have 2 question from the above given code snippet which I have mentioned in the comments of the code above.

As can be seen from the code, the control can never flow over the end of the function func because a will never be equal to b inside the function since I have checked that condition in main separately.

cigien
  • 57,834
  • 11
  • 73
  • 112
Jason
  • 36,170
  • 5
  • 26
  • 60
  • @user17732522 I have moved `func(x,y)` inside the `if(x!=y)` condition. Check out my update. Is it still UB? – Jason Mar 17 '22 at 05:50
  • No call to `func` is flowing off the end of the function now, so there is no UB anymore. Whether the return value is used (1 vs 2) still doesn't matter. – user17732522 Mar 17 '22 at 05:52
  • @user17732522 Ok but the compiler still gives warning that: *"control reaches end of non-void function"*. – Jason Mar 17 '22 at 05:54
  • @Rick: Compilers can warn about whatever they want. – Nicol Bolas Mar 17 '22 at 05:54
  • @Rick Because someone could still call the function with equal arguments for which a return statement is missing. That is likely to be a mistake, so the compiler implements a warning for it. – user17732522 Mar 17 '22 at 05:55
  • @user17732522 Ok to confirm that i understand the topic clearly will it be correct to say that: *"What matters is that the control flow doesn't exit a non-void function without executing a `return` statement(which happens at runtime) and not that whether the caller uses the return value in C++"*? Can you confirm if this is correct. – Jason Mar 17 '22 at 06:15
  • No, that's not right. Exiting by e.g. throwing an exception does not violate the requirement. This applies only to reaching the end of the function. – user17732522 Mar 17 '22 at 06:22
  • @user17732522 Yes i know about exceptions. In particular that it doesn't apply to throwing exception. My above statement was only in the context of `return` statement. – Jason Mar 17 '22 at 06:24
  • @user17732522 I have made a comment in Nicols' answer. It is a query. If you don't mind you can comment on that since Nicol seems to be busy at the moment. Or you can add an answer separately in some more detail. I am still somewhat confused about is there a difference(if any) between exiting a function and flowing off the end of a function. To me they mean the same thing. – Jason Mar 17 '22 at 06:26
  • "Flowing off the end of the function" means reaching the closing `}` of the function. Exiting is a more general term which also includes other means by which the function execution can be ended, for example throwing exceptions. I think the already posted answer is quite clear already. – user17732522 Mar 17 '22 at 06:34
  • @user17732522 Ok now i am starting to understand the answer by Nicol. Thanks for the discussion which helped me clarify things. Just one last query, in case of `exit` and `std::terminate` the phrase *"the control flow does not exit the function at all"* **is the same as** saying that *"for these functions the control flow never reaches the closing `}`"* right? – Jason Mar 17 '22 at 06:40
  • @Rick Not exiting logically implies not flowing off the end, but not the other way around. – user17732522 Mar 17 '22 at 06:45
  • @user17732522 So `exit` and `std::terminate` does not exit logically which implies not flowing off the end. But in my question's given snippet, we're not flowing off the end but that does not imply that we're not exiting logically. – Jason Mar 17 '22 at 06:50
  • Is the language revision (i.e. C++14) actually relevant to your question? If not, I'd suggest removing it. – cigien Mar 17 '22 at 13:20

1 Answers1

8

The two statements are in no way contradictory.

The first statement is about what happens when control flow exits a non-void function without executing a return statement. The second statement is about what happens when control flow does not exit the function at all. Calls to functions like exit or std::terminate do not ever have control flow proceed past the point when those functions are called.

But that has nothing to do with the nature of the return value.

The behavior of the program when a non-void function runs out of stuff to do without an explicit return statement (or throw. Or co_return these days) is governed by [stmt.return]/2:

Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • So if i understood it correctly does it mean that a call to `exit` or `std::terminate` is not UB because at runtime the control flow for these two functions never goes beyond the closing `}` of those functions respectively? Which means technically we're not flowing off the end of those functions and hence no UB? – Jason Mar 17 '22 at 06:04
  • 1
    @Rick • That is correct. – Eljay Mar 17 '22 at 12:27
  • @Rick: They do not return by fiat, that is, because the C++ standard *says so*. Just like the standard says that your executable starts with `main` but after all global variable initialization. How that happens is up to the implementation. Also, `terminate` is explicitly `noexcept` – Nicol Bolas Apr 12 '22 at 13:52
  • 1
    If a function **throws**, regardless if it's return type is `void` or something else, then the function did not *return*. (A coworker of mine calls this an "invisible goto", and he's not a fan of C++'s exception mechanism.) – Eljay Apr 12 '22 at 19:13