I just read in K&R that a function, even if not defined as void, need not return a value. To test I wrote a quick power function and I'm getting reproducible yet unexpected behaviour:

✈demios:cliC$ cat power.c 
#include <stdio.h>

int power(int x, int y);

int main() {
        printf("%d\n", power(2,5));
        return 0;
}//end main

int power (int x, int y){
        int i,total=1;
        for (i=0;i<y;i++) {
                total*=x;
        }
}//end power
✈demios:cliC$ cc power.c 
✈demios:cliC$ ./a.out
5
✈demios:cliC$

Why is the function returning 5? I would expect 0 or null or NAN or some such thing. Why 5? I know not to rely on this behaviour, and such code would never see production, but I would like to understand it.

Thanks.

Recommended Answers

All 10 Replies

I just read in K&R that a function, even if not defined as void, need not return a value.

What page? From memory, the only place where K&R makes such a grievous mistake is in the beginning tutorial with the initial definitions of main. However, that's a pedagogical decision that they clearly explain a bit later.

Why is the function returning 5?

The usual explanation is that it's undefined behavior and anything could happen. But to speculate about what's happening in your case, 5 is coming from whatever was in already in the location where return values are stored. As an illustrative example, let's consider a hypothetical compiler that uses a hidden pointer parameter for return values. If you return a value it might look like this after compiler translation:

void power(int *__$retv, int x, int y)
{
    int i, total = 1;

    for (i = 0; i < y; i++)
        total *= x;

    *__$retv = total;
}

void main(int *__$retv)
{
    int __$power$retv;
    int __$printf$retv;

    power(&__$power$retv, 2, 5);
    printf(&__&printf&retv, "%d\n", __$power$retv);

    *__$retv = 0;
}

Without a return statement to translate, the power function would look more like this:

void power(int *__$retv, int x, int y)
{
    int i, total = 1;

    for (i = 0; i < y; i++)
        total *= x;
}

Notice how *__$retv is never set, and the hidden temporary in main is a local variable without any default initialization. This means that if you don't provide a return value in this hypothetical compiler, the return value of the function will be whatever was already on the stack for __$power$retv .

The statement in question is in fact in the first chapter, at the beginning of page 26:

A function need not return a value; a return statement with now expression causes control, but no useful value, to be returned to the caller, as does "falling off the end" of a function by reaching the terminating right brace.

Note that in this case there is in fact a "useful value" returned, which is 5. Bug? Error in the text?

Thanks.

Note that in this case there is in fact a "useful value" returned, which is 5.

Just how is 5 useful? Sure, it's the correct type, but the value itself is completely unpredictable garbage.

Bug? Error in the text?

Error in reading the text. You apparently read it as "I can define a function that claims to return a value, not return a value, and magic happens". The actual meaning is that functions are not required to return a value. This is as opposed to languages that differentiate between procedures (which don't return a value in all cases) and functions (which do return a value in all cases).

Note that the first chapter is intentionally simplified, so many of the nuances will be left out. In practice, functions with a return type of void cannot return a value (you can use an empty return statement or fall of the end) while functions with any other return type must return a value or risk undefined behavior.

commented: rep++ +17

Just how is 5 useful? Sure, it's the correct type, but the value itself is completely unpredictable garbage.

That is exactly my point. Why did it return 5? Well, I actually know why (5 was the value in the particular spot in memory) but why did it return anything at all?

Error in reading the text. You apparently read it as "I can define a function that claims to return a value, not return a value, and magic happens". The actual meaning is that functions are not required to return a value. This is as opposed to languages that differentiate between procedures (which don't return a value in all cases) and functions (which do return a value in all cases).

Actually, I understood it as "I can define a function that claims to return a value, not return a value, and either nothing will be returned or whatever is returned will not be a garbage value. It would be null, 0, or some other logical value." In other words, I understood "no useful value" to mean "no value" not "a useless value".

I suppose that this sentence vagary is more common that I thought. I see politicians use it all the time, such as "We do not condone that action", which implies that the politician in question will hold the action against the offender (to condone is to forgive), but really means that the politician will do nothing. I need to learn to read between the lines.

Note that the first chapter is intentionally simplified, so many of the nuances will be left out. In practice, functions with a return type of void cannot return a value (you can use an empty return statement or fall of the end) while functions with any other return type must return a value or risk undefined behavior.

Thanks, it is exactly the undefined behaviour aspect that I found irksome. I now understand my folly. Thanks.

but why did it return anything at all?

Because you told it to:

int power(int x, int y);

This says "I will return an integer value". Even if you don't explicitly return something, the compiler will generate code to return a value. The compiler also won't stop calls to this function from using the return value. The assumption is that you, the programmer, didn't lie about returning a value. Since you lied, and didn't return a value, you get what you deserve: undefined behavior.

It would be null, 0, or some other logical value.

Okay, how do you plan to represent some logical value without eliminating one valid value from the type? Say you're returning int, every value in the range of int is perfectly valid. So which of those values shall be made not valid such that it can be used as the logical "null" value in the case where a programmer fails to return something? Further, why should the compiler cater to broken code at the expense of valid code?

I need to learn to read between the lines.

Maybe you're the kind of person who's better off just reading the standard rather than tutorials. At least then you'll have the painfully precise language lawyerese.

commented: Thank you for your patience explaining. +2

Because you told it to:

int power(int x, int y);

This says "I will return an integer value". Even if you don't explicitly return something, the compiler will generate code to return a value. The compiler also won't stop calls to this function from using the return value. The assumption is that you, the programmer, didn't lie about returning a value. Since you lied, and didn't return a value, you get what you deserve: undefined behavior.

Without a doubt! The parts that I did not understand were:
1) Where did the "5" come from? Why specifically "5"? I now understand that "5" just happened to be in the memory from some earlier event. Though, I can recompile and run and still get "5" so I actually think that there is something intrinsic about the code that gives "5".

Okay, how do you plan to represent some logical value without eliminating one valid value from the type? Say you're returning int, every value in the range of int is perfectly valid. So which of those values shall be made not valid such that it can be used as the logical "null" value in the case where a programmer fails to return something?

The return value need not be a non-valid int. Had it returned "0" then I would at least understand from where the value returned came from. As it is, "5" is a mystery. But this isn't the important part, my reply to your next question addresses the important part.

Further, why should the compiler cater to broken code at the expense of valid code?

I never suggested that the compiler should cater at all to the situation in which one does not return a value other than a compilation warning or failure. Why isn't there a warning or failure? Neither cc nor gcc complains, even with the -pedantic option.

Maybe you're the kind of person who's better off just reading the standard rather than tutorials. At least then you'll have the painfully precise language lawyerese.

Actually, I was under the impression that K&R was the standard for many years! In any case, I was not questioning the compiler's competence, rather, I was asking _how_ it came to the value 5. I might enjoy looking at the C standard sometime, as well-written engineering standards are actually very good reads. I've never read a programming standard, though. But back to the point: I do not think that there is denying the ambiguity of the statement in question, though I failed to recognize ambiguity when I came to the conclusion that I had come to. I should have recognized the ambiguity and then discovered empirically (code experimentation, which I did perform) the correct meaning.

Thank you for your patience in explaining what I see is a passionate subject for you.

1) Where did the "5" come from? Why specifically "5"?

You don't need to know or care yet. When you gain proficiency and want to look into how compilers work at a lower level, fine. But for now, studying how undefined behavior works in practice will just distract you from the task of learning proper C. Further, it might encourage you to rely on dangerous behavior.

Had it returned "0" then I would at least understand from where the value returned came from.

Where would the value of 0 come from if your code didn't explicitly return it? I fail to see how 5 is baffling and 0 is not when they're both complete garbage.

Why isn't there a warning or failure? Neither cc nor gcc complains, even with the -pedantic option.

Your warnings aren't turned up high enough, because I get a warning on all of my compilers. Try -Wall and -WExtra.

Actually, I was under the impression that K&R was the standard for many years!

K&R is split into two parts: a tutorial and a reference. The reference served as something of a poor man's standard until ANSI and ISO rolled out a sufficiently large tome. Provided you follow the best practices given in the book, your own code will be pretty good.

Though, I can recompile and run and still get "5" so I actually think that there is something intrinsic about the code that gives "5".

I just ran your code in gcc and got 0. So there's nothing intrinsic at all. It's a random value.

You don't need to know or care yet. When you gain proficiency and want to look into how compilers work at a lower level, fine. But for now, studying how undefined behavior works in practice will just distract you from the task of learning proper C. Further, it might encourage you to rely on dangerous behavior.

Fair enough! I'll wait until I get far more proficient before examining that. Thanks.

Your warnings aren't turned up high enough, because I get a warning on all of my compilers. Try -Wall and -WExtra.

Thanks. I think that I will use -Wall on all my compiles, at least while I am learning.

K&R is split into two parts: a tutorial and a reference. The reference served as something of a poor man's standard until ANSI and ISO rolled out a sufficiently large tome. Provided you follow the best practices given in the book, your own code will be pretty good.

I will be diligent in learning those best practices. Thanks.

I just ran your code in gcc and got 0. So there's nothing intrinsic at all. It's a random value.

Thanks, that is good to know.

Again, thank you for your patience and your willingness to teach!

IIRC, if a function that is declared to return a value falls off the end without doing so, the C89 standard permits this state of affairs so long as the caller does not try to use the value that the function failed to return. In other words:

int foo()
{
    /* This function should return an int but doesn't */
}

int main()
{
    foo();      /* Discards the invalid value, so the call is OK */
    foo() + 0;  /* Undefined behavior */
}

I do not remember for sure whether C99 tightened this rule.

If you use
power(2, 7)
with the missing return the output is 7, this might give you a hint.
Moral, use return or you get gibberish!

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.