Even I get an warning a function returns an address from local
variable, it compiles. Isn’t it then UB of compiler?
No, but if it were, how could you tell? You seem to have a misunderstanding of undefined behavior. It does not mean “the compiler must reject it”, “the compiler must warn about it”, “the program must terminate”, or any such thing. Those indeed may be a manifestations of UB, but if the language specification required such behavior then it wouldn’t be undefined. Ensuring that a C program does not exercise undefined behavior is the responsibility of the programmer, not the C implementation. Where a programmer does not fulfill that responsibility, the C implementation explicitly has no reciprocal responsibility — it can do anything within its capabilities.
Moreover, there is no single “the” C compiler. Different compilers may do things differently and still conform to the C language specifications. This is where implementation-defined, unspecified, and undefined behaviors come in. Allowing such variance is intentional on the part of the C language designers. Among other things, it allows implementations to operate in ways that are natural for their particular target hardware and execution environments.
Now let’s go back to “no”. Here is a prototypical example of a function returning the address of an automatic variable:
int *foo() {
int bar = 0;
return &bar;
}
What about that is supposed to have undefined behavior? It is well defined for the function to compute the address of bar
, and the resulting pointer value has the correct type to be returned by the function. After bar
‘s lifetime ends when the function returns, the return value becomes indeterminate (paragraph 6.2.4/2 of the standard), but that does not in itself give rise to any undefined behavior.
Or consider a caller:
void test1() {
int *bar_ptr = foo(); // OK under all circumstances
}
As already discussed, our particular foo()
‘s return value will always be indeterminate, so in particular, it might be a trap representation. But that’s a runtime consideration, not a compile-time one. And even if the value were a trap representation, C does not require that the implementation refuse or fail to store it. In particular, footnote 50 to C11 is explicit on this point:
Thus, an automatic variable can be initialized to a trap
representation without causing undefined behavior, but the value of
the variable cannot be used until a proper value is stored in it.
Note also that foo()
and test1()
can be compiled by different runs of the compiler, such that when compiling test1()
, the compiler knows nothing about the behavior of foo()
beyond what is indicated by its prototype. C does not place translation-time requirements on implementations that depend on the runtime behavior of programs.
On the other hand, the requirements around trap representations would apply differently if the function were modified slightly:
void test2() {
int *bar_ptr = NULL;
bar_ptr = foo(); // UB (only) if foo() returns a trap representation
}
If the return value of foo()
turns out to be a trap representation, then storing it in bar_ptr
(as opposed to initializing bar_ptr
with it) produces undefined behavior at runtime. Again, however, “undefined” means just what it says on the tin. C does not define any particular behavior for implementations to exhibit under the circumstances, and in particular, it does not require that programs terminate or manifest any externally-visible behavior at all. And again, that’s a runtime consideration, not a compile-time one.
Furthermore, if foo()
‘s return value turns out not to be a trap representation (being instead a pointer value that is not the address of any live object), then there’s nothing wrong with reading that value itself, either:
void test3() {
int *bar_ptr = foo();
// UB (only) if foo() returned a trap representation:
printf("foo() returned %p\n", (void *) bar_ptr);
}
The biggest and most commonly-exercised undefined behavior in this area would be that of trying to dereference the return value of foo()
, which, trap representation or not, almost surely does not point to a live int
object:
void test4() {
int *bar_ptr = foo();
// UB under all circumstances for the given foo():
printf("foo() returned a pointer to an int with value %d\n", *bar_ptr);
}
But again, that’s a runtime consideration, not a compile-time one. And again, undefined means undefined. The C implementation should be expected to translate that successfully as long as there are in-scope declarations for the functions involved, and although some compilers might warn, they have no obligation to do so. The runtime behavior of function test4
is undefined, but that does not mean the program necessarily will segfault or terminate in some other manner. It might, but I expect that in practice, the undefined behavior manifested by a great many implementations would be to print “foo() returned a pointer to an int with value 0”. Doing so is in no way inconsistent with C’s requirements.
2
solved function returns address of local variable, but it still compile in c, why?