0

I was writing a code to swap the elements of 2 arrays, my colleague wrote the function as a pointer to integer return value and in main he didn't receive the returning pointer, yet the code worked perfectly, why is this possible?, I will try to include some code below:

int * Swap (int a_size,int *a,int b_size,int *b){
    int i;
    int temp;
    for(i=0;i<b_size;i++){
        temp=a[i];
        a[i]=b[i];
        b[i]=temp;
    }
}
Steve Summit
  • 45,437
  • 7
  • 70
  • 103
Nader Atef
  • 27
  • 4
  • 2
    If a function does not return a value when it has been declared to do so it invokes *undefined behavior* - it may appear to work correctly, but that could change at any moment – UnholySheep Aug 12 '21 at 14:30
  • @UnholySheep what if we added a return in this function but also we receive the return value in main, is it still correct? – Nader Atef Aug 12 '21 at 14:31
  • 1
    In C exiting from non-void function without `return` statement is OK. (FYI it causes undefined behavior in *C++* if the function is not `main`) Using values "returned" without `return` statement causes undefined behavior, but in this case the "returned valeu" is discarded, so it won't cause problem. (Actually the `Swap` function is not shown to end, so there may be `return` statement after the disclosed part?) – MikeCAT Aug 12 '21 at 14:32
  • @MikeCAT even if we added a return value, can we call the function without receiving the return value? – Nader Atef Aug 12 '21 at 14:34
  • @NaderAtef Yes. For example, have you used `printf()`? If yes, did you store the return value of that? – MikeCAT Aug 12 '21 at 14:35
  • @MikeCAT is that a good practice? – Nader Atef Aug 12 '21 at 14:37
  • NaderAtef Not good practice - exceptions exist, yet [@MikeCAT](https://stackoverflow.com/questions/68759331/do-we-always-have-to-assign-the-return-value-of-a-function-in-c#comment121517477_68759331) is correct about what is possible - the title question was "always have to", not "good practice". – chux - Reinstate Monica Aug 12 '21 at 14:49
  • Not what you asked, and I don't mean to insult your colleague, but this `Swap()` function is a strange and rather poor one. It ignores the `a_size` parameter. Not only does it not return an `int *` value as promised, there is no reason for it to return an `int *` value in the first place. It should be declared as returning `void`. – Steve Summit Aug 12 '21 at 14:59
  • 2
    @UnholySheep: Re “If a function does not return a value when it has been declared to do so it invokes undefined behavior”: No, it does not. MikeCAT’s comments on this are correct. The C standard does not say a function must return a value or that, if it does not, the behavior is undefined. If the value is **used** by the caller, then the behavior is undefined, because C 2018 6.9.1 12 says “Unless otherwise specified, if the `}` that terminates a function is reached, and the value of the function call is used by the caller, the behavior is undefined.” (It is otherwise specified for `main`.) – Eric Postpischil Aug 12 '21 at 15:14
  • @NaderAtef The situation exactly as you have described it is (barely) okay. If the caller began to use the returned value there would be a problem. If the `Swap` function were changed to return a value, or to be declared as `void`, it would be better, because it would avoid that potential problem. See the longer answers below for more details. – Steve Summit Aug 12 '21 at 15:54
  • What’s with these close reasons? This question is not caused by a typo and reproducibility is not an issue. – Eric Postpischil Aug 12 '21 at 16:28

4 Answers4

3

I guess there are sort of two questions here:

  1. If a function is declared to return a value (or not), how wrong is it to not return a value (or to return a value)?
  2. If a function returns a value (or not), how wrong is it for the caller not to use the value (or to use the value)?

Let's examine these in turn.

First, if a function is declared to return a value (or not), how wrong is it to not return a value (or to return a value)?

There are four cases:

  1. function declared to return value, function does return value: this is obviously good and proper.
  2. function declared to return void, function does not return value: this is obviously good and proper.
  3. function declared to return void, function does return value: this is obviously an error, and the compiler will emit an error message and halt compilation.
  4. function declared to return value, function does not return value: this is probably an error, but for historical reasons, the compiler might not complain. Good compilers will at least issue a warning here. It sounds like your compiler didn't. You might want to think about getting a better compiler, or changing compilation options so it issues more warnings. (It's also possible for this situation not to be an error, as described in Eric Postpischil's answer, and this helps explains why the situation is not, in fact, illegal. There may also be subtle differences between the case where the function "falls off the end" and hits the terminating } without ever having a return statement, versus the case where it has an explicit return; statement without a value.)

That might answer your question, but if not, let's move on to my second question: if a function returns a value (or not), how wrong is it for the caller not to use the value (or to use the value)?

Again, there are four cases:

  1. function declared to return value, caller uses return value: this is obviously good and proper.
  2. function declared to return void, caller does not use return value: this is obviously good and proper.
  3. function declared to return void, caller does use return value: this is obviously an error, and the compiler will emit an error message and halt compilation.
  4. function declared to return value, caller does not use return value: this is the interesting case. It might be a problem, or it might not be a problem. Let's expand on this further:

For a function like sqrt, whose sole function is to compute and return a value, it would obviously be very strange to not use the return value. For example, if you write

sqrt(100);

alone on a line, you're asking to compute the square root of 100, and then throwing away the return value. This is not an error, but a good compiler will probably warn about it, because it's probably not what you want.

There are other functions that return a value, that you might not care about. For example, it's not well known that the venerable printf function returns a value: the number of characters printed. But printf is not a function whose sole function is to compute and return a value; printf also does something interesting off to the side: it prints stuff. So if you call

printf("Hello, world!\n");

but if you throw away the return value, that's fine, almost no one would object to that. (Theoretically there's a chance that printf might fail, due to some kind of an i/o error, and return -1 to try to tell you this, but most programs don't care about this, and in the grand scheme of things that's not a problem.)

Finally, there are programs that return a value, that you might not think you care about, but that you probably should care about. Consider this fragment:

int i;
printf("enter an integer:\n");
scanf("%d", &i);
printf("you typed %d\n, i);

Here, the problem is that if the user types something like "no, I won't", scanf will be unable to read an integer as requested by %d, and it will return 0 to say that it performed no conversion, but the program won't notice, and since i is an uninitialized value, it's hard to see what the program might print.

If we think about this carefully, we see that there might be sort of a "hidden" attribute of functions, namely: "How okay is it for the caller to ignore the return value?" printf is a function that returns int, but it's usually (in all but the most paranoid programs) okay to ignore the return value. But scanf is a function that returns int where, as we've just seen, it's not a good idea to ignore the return value (although lots of programmers do). And sqrt is a function returning double, where it's almost certainly an error not to use the return value. So if a compiler decides to help you out by warning you when you forget to use a function's return value, how does it avoid spamming you with warnings every time you call printf? And the answer is that such a compiler has to invent a special way to implement that property -- a property not defined by the C language, which is why I called it "hidden" -- that specifies whether or not the warning is appropriate.

Also -- and this gets down to the meat of your question as asked -- there's a higher-order question that sort of combines the two top-level questions I've been exploring: If a function is declared as returning a value, but does not return a value, what happens if the caller does/doesn't try to use the returned value?

And the answer is that if a function is declared as returning a value, but it doesn't actually return a value, but the caller doesn't try to use the return value, that's fine -- although this might seem like kind of a dicey situation! And on the other hand, if the caller does try to use the value that wasn't returned, that's obviously a bad situation. As far as the C Standard is concerned, it falls into the dreaded category of undefined behavior, and there's a pretty good reason for this: deciding whether it's actually a problem is a hard, almost impossible problem. If a function is declared as returning a value, and if it sometimes doesn't return a value, and if the caller sometimes doesn't try to use the returned non-value, deciding whether it's actually a problem would theoretically requiring (a) examining the code for both the function and its caller and (b) deciding under which circumstances the function does or doesn't return a value. Both of these subproblems are basically impossible to solve. C supports separate compilation, so in the general case a compiler won;t be able to inspect both the function and its caller at once. And solving subproblem (b) is basically the halting problem. So it's up to you, the programmer, to keep things straight in this case: the compiler can't be required to issue an error if you try to use the value that wasn't returned by a function declared as if it returned one.

In the end, though, the Swap() function you posted could probably an example of the "don't care" case. It's defined as returning int *, but it doesn't actually return a value, but the caller doesn't try to use it, so it's okay. But this is potentially quite confusing and error-prone, so I would suggest that it would be much better to either (a) have it actually return a value or (b) redeclare it as returning void.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • 2
    Re “this is obviously an error”: No, it is not. `int DoCommand(int Command, int value) { switch (Command) { case Get: return SavedValue; case Set: SavedValue = value; break; } }` sometimes returns a value and sometimes does not and is not in error because of it. – Eric Postpischil Aug 12 '21 at 15:16
  • @EricPostpischil Nice example. I was thinking of the old days, before `void` existed, when non-return was by convention, and couldn't be definitively warned about (though I think `lint` tried). But you're right: someone can still write a "conditionally valued" function like this today -- although we can argue over whether it's good style or not. I've softened the "obviously", but I'm not sure I want to complicate a too-long answer with a digress on this point or not. – Steve Summit Aug 12 '21 at 15:31
  • @SteveSummit Thank you Steve for the detailed answer – Nader Atef Aug 12 '21 at 19:30
2

The C standard does not require a function declared with a non-void return type to return a value. It is not, by itself, undefined behavior for such a function not to return a value.

It is undefined behavior if the caller attempts to use the value of a function that did not return a value. C 2018 6.9.1 12 says:

Unless otherwise specified, if the } that terminates a function is reached, and the value of the function call is used by the caller, the behavior is undefined.

(It is “otherwise specified” for main: In a hosted environment, if the return type of main is int and it reaches the } that terminates its body, it returns zero, per C 2018 5.1.2.2.3 1.)

In most cases, it is good practice for a function declared with a non-void return type to return a value, and compilers may warn if they do not. However, there are situations in which a function may be intentionally designed not to return a value in all cases. For example:

typedef enum { Get, Set } CommandType;

int DoCommand(CommandType Command, int Value)
{
    switch (Command)
    {
        command Get:
            return SavedValue;
        command Set:
            SavedValue = Value;
            break;
    }
}

This is a very simplified abstracted example—with simple situations, we might want to separate this routine into two routines, one to set and one to get. But in more complicated software, it might make sense to wrap these and other “commands” that operate on various objects (not shown) into a single command-and-control routine which sometimes returns a value and sometimes does not.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
1

In order to write correct C programs, one should always indicate the correct return type of the function. In case a function does not return any value, a "special" return type void is used.

Example function that returns an int:

int add(int a, int b) {
  return a + b;
}

Example function that returns nothing:

void swap(int* a, int* b) {
  int temp = *a;
  *a = *b;
  *b = temp;
}

You can ignore the return value of a function if you don't need it:

void test(void) {
  int x = add(3, 5); // correct, we're using the return value of add()

  add(1, 2); // also correct, we're just ignoring the return value of add()

  (void) add(1, 2); // more correct, this is just like previous line, but we're making it obvious to the reader that we are explicitly ignoring the return value, and it is not a mistake
}

EDIT: in order to make my answer more useful, I will add some examples of what SHOULD NOT be done in C.

Example 1:

// WRONG, because we defined neg() to return an int, but we're not returning it
// NOTE: this is wrong even if we ignore the return value of neg()
int neg(int* a) {
  *a = -(*a);
}

// POSSIBLE CORRECT ALTERNATIVE
void neg(int* a) {
  *a = -(*a);
}

// ANOTHER POSSIBLE CORRECT ALTERNATIVE
int neg(int* a) {
  *a = -(*a);
  return *a;
}

Example 2:

// WRONG, because we defined foo() to return nothing, but we're actually returning an int
void foo(int a) {
  return a;
}

// CORRECT ALTERNATIVE
int foo(int a) {
  return a;
}

Example 3:

// CORRECT
void foo(int a, int b) {
  if (a == 0) {
    return; // we're returning early from the function (and we're returning nothing, of course, since function is void)
  }

  printf("%d %d\n", a, b);

  return; // this is really not needed and it is implicit in void functions: when they reach the end, they return nothing
}

// WRONG
int bar(int a, int b) {
  if (a == 0) {
    return; // WRONG, we must return an int
  }

  int x = foo(1, 2); // WRONG: foo() returns nothing (i.e.: void), and we cannot therefore use the return value of foo()

  // WRONG: reached end of function, but we never returned an int
}

Example 4:

void test(void) {
  printf("Hello, I'm ignoring your return value.\n"); // CORRECT, printf() is a standard function that returns the number of characters written (returned as an int), and if we don't need this information, we can just ignore the returning value

  malloc(500); // WRONG, malloc() is a standard function defined to return a pointer, and it is one of many functions that we should never ignore the return value of

  // The difference between printf() and malloc() must be learned from the docs: there we can find out that printf()'s return value can be safely ignored, while malloc()'s cannot
}

EDIT 2: in my answer the words CORRECT and WRONG are not always used to say "it is correct/wrong according to the ISO C standard", but sometimes (as in the example of malloc() vs printf()) they mean "it is correct/wrong according to good programming practices"

Luca Polito
  • 2,387
  • 14
  • 20
  • @luca OK answer, but skirts an issue: is it well defined to not return a value on a non-`void` fucntion if the calling code does not use the return value? – chux - Reinstate Monica Aug 12 '21 at 14:55
  • @chux-ReinstateMonica I updated my answer with more examples, that also cover the question in your comment. – Luca Polito Aug 12 '21 at 15:49
  • `int neg(int* a) { *a = -(*a); }` is not good practise, yet OK if calling code does not use the `neg()` return value. – chux - Reinstate Monica Aug 12 '21 at 15:59
  • Example 4: `int main()` does not return anything, which is OK as `int main()` lacking a return is special. – chux - Reinstate Monica Aug 12 '21 at 16:00
  • @chux-ReinstateMonica `neg()` function indeed is correct per the ISO C standard (as long as its return value is not used), BUT may easily (and, from experience, very often) trigger undefined behavior since a "distracted" developer could use its return value, not knowing that it cannot be used. So it is wrong (i.e.: not good practice, and should be avoided as much as possible, even if it is not forbidden). A lot of bugs and vulnerabilities are caused by this. – Luca Polito Aug 12 '21 at 17:01
  • @chux-ReinstateMonica About `int main()` not returning an `int`, it was an oversight, mea culpa. Even if it is correct, it's always good practice returning an `int` when `main()` is defined as `int main()`. I edited the answer replacing `int main()` with `void main()`. – Luca Polito Aug 12 '21 at 17:05
  • Best to distinguish what is possible with good practice. Note: `void main()` will likely not meet _good practice_. UV for "one should always indicate the correct return type of the function" and then return that type when not `void`. – chux - Reinstate Monica Aug 12 '21 at 17:15
  • Yes, `void main()` is non-standard, mea culpa (really, I just used it in my answer as a dumb test function, now I corrected it). But `int main()` should always return an `int`, in order to be consistent (all other `int` functions must and so should `main()`) and to avoid unexpected behavior (i.e.: what will be the exit status of your program if you do not return an `int` from `main()`? Yes, you could search in the docs (answer is `0`), but that would not be as obvious as to directly returning an `int` from `main()`). And other programmers may guess wrong when you're not explicit. – Luca Polito Aug 12 '21 at 17:29
0

Yes. Always do a clean return from any non-void function.
Otherwise the consequences are

  • confused colleagues
  • confused future you
  • more risks at code changes
Yunnosch
  • 26,130
  • 9
  • 42
  • 54