Wait, undefined parameters are guaranteed to be zero in C? That's something I wouldn't have expected. Certainly nothing I would rely on in my own code.
No, not at all. It doesn't run the program, the program isn't correct, it just compiles and links. Symbols don't know their types (... with some caveats about name mangling in some languages).
In C, a function with no parameters is variadic. You need to add "void" to preclude parameters. This is one of the few breaking changes in C++ where no parameters means no parameters.
It's legal to declare an external function symbol without specifying the parameters, so long as you actually match the types of real parameters (after all the standard conversions for functions with such prototypes are taken into account, like char -> int; and excluding some corner cases, like varargs) when you call it. If you don't actually call it, it's not U.B.
However, the snippet above is still broken in that respect, because it doesn't match the return type of memcpy - and that is U.B.
Right, but (again, ignoring the return type) the call doesn't actually get executed, and U.B. only happens if it does. Though I guess they could have wrapped it in if(0){...} to make this completely unambiguous.
Is your claim "there is no undefined behavior because the program is not run"? While that's true (there's no behavior, much less undefined behavior), in that case I don't see why the return type deserves calling out special. This program exhibits undefined behavior iff executed, and that would be true even if the return type were void * .
> I guess they could have wrapped it in if(0){...} to make this completely unambiguous.
Depending on optimizations, that may mean the executable does not depend on the symbol we're trying to test the presence of.
Indeed, on my local gcc that's the case, even with -O0.
$ cat test.c
void *memcpy();
int main() {
if(TEST) {
(void)memcpy();
}
}
$ gcc -DTEST=0 test.c
$ objdump -T a.out
a.out: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
$ gcc -DTEST=1 test.c
$ objdump -T a.out
a.out: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.14 memcpy
$ gcc -O0 -DTEST=0 test.c
$ objdump -T a.out
a.out: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
c.f.:
$ cat test.c
void *blah();
int main() {
if(TEST) {
(void)blah();
}
}
$ gcc -DTEST=1 test.c
/tmp/cc2Vk5mE.o: In function `main':
test.c:(.text+0xa): undefined reference to `blah'
collect2: error: ld returned 1 exit status
$ gcc -DTEST=0 test.c
$ objdump -T a.out
a.out: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
$ gcc -O0 -DTEST=0 test.c
$ objdump -T a.out
a.out: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
I'm just trying to draw the line between U.B. that is guaranteed to happen at runtime (at which point the compiler is, technically speaking, not even obligated to provide you with a binary to run), and U.B. that will only happen if that particular code is executed, but doesn't trigger if that branch is omitted. It's probably overly pedantic, but given what modern optimizing compilers do, I'm in the "better safe than sorry" camp.
You definitely have a point regarding compiler just ripping that code out. I guess the proper approach would be to do something like if(argv[0][0]), or test a volatile variable.