Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.


True, but I'm not seeing the relevance. Just an interesting aside?


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.


> It's legal to declare an external function symbol without specifying the parameters, so long as you actually match the types of real parameters

Yes. And the call here does not match the types of the real parameters, even before we consider the return type.


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.


But in this case, the U.B. is guaranteed to happen at runtime, even with the proper return type. There are no branches involved here...


Yes, you're right. I was thinking aloud about how it could be made conforming, in principle.


Gotcha.


No. Undefined parameters are not guaranteed to be zero in C. Static variables not explicitly set are guaranteed to be initialized to zero in C.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: