If your C compiler doesn't complain bitterly to you about an incompatible pointer type on that assignment, get a better C compiler. If your C compiler does complain bitterly about it, heed those warnings or expect no sympathy.
If you write this in production code, and don't get sent home that day in tears, your company's code review process has failed.
> If your C compiler doesn't complain bitterly to you about an incompatible pointer type on that assignment, get a better C compiler.
The example code uses C style casts, which is C-speak for "fuck off compiler I know what I'm doing." I'd like to see what warnings your compiler produces. VS2015 /W4 /ANALYZE produces nothing, and neither do clang nor GCC from the looks of it.
That people C cast without knowing what they're doing is rather unfortunate tragedy. That this is a common enough problem, that the Win32 API is littered with backwards compatability hacks where someone C-casted away ABI differences turns it into a statistic.
> If you write this in production code, and don't get sent home that day in tears, your company's code review process has failed.
Assuming we're talking "well meaning newbie" instead of "sanity hating self-flagellating freak and/or undefined behavior worshiping cultist", I prefer to carefully explain that they've invoked undefined behavior, that undefined behavior leads to crunching to find optimizer induced heisenbugs in the weeks before we ship, and to impress upon them that this is why we never cast away even "trivial" and "minor" function pointer differences. And then, when they ask what the correct thing to do is, point out the API docs and header both have this little "WINAPI" annotation they can use. Hey! No more casts! Easier than the casts too. Win/win.
However, if we are talking about the "sanity hating self-flagellating freak and/or undefined behavior worshiping cultist", I can only recommend nuking the site from orbit. It's the only way to be sure. If their home isn't part of the radioactive crater, you didn't use large enough nukes, and your company's code review process has failed.
I often remind my fellow engineers to keep their code muggle-friendly. And, mind you, our code base is mostly Python, a language that actively avoids black magic by design.
I'd hate to work at a company with a blanket prohibition on dirty tricks like this. On rare occasions, there are good reasons to use hacks, and I'd hope my coworkers would at least be open to an attempt at justification. Sometimes silly newbie mistakes actually end up being the right thing to do in rare, specialized circumstances and in experienced hands.
I can't ever imagine a scenario where a hack _like_ _that_ is acceptable.
If you need something like that, just drop down to assembly. It's more portable (in the sense that you're coding to an interface), infinitely more clear about what you're doing and and how you're doing it, much less likely to break, and when it does break is likely to break loudly.
Even if you've never done a lick of assembly programming (I haven't done much) it'd still be infinitely preferable, even if you had to support a dozen architectures with two dozen different function proxies.
"Hack" is a very broad term. By some definitions a significant chunk of extant code qualifies as a hack. But whatever the definition, a hack still implies some quantum of legitimacy. There's nothing legitimate in using function type punning as a way to reorder or control argument values. Not unless this was 1969, where there was precisely one C compiler, used by precisely one guy, and where such code was expected to be thrown out in short order, never to infect code that executed in other than identical circumstances.
Dirty tricks are almost never worth the long term technical debt they represent when either (1) weird bugs start showing up or (2) the original author leaves the company.
I used to believe that there were times when dirty tricks were the right call in certain cases. The problem was that the long term view always proved me wrong: a more verbose but commonly understood approach is much more maintainable than a concise dirty trick, even with ample commenting and documentation.
> On rare occasions, there are good reasons to use hacks
Yes.
However.
If you wish to convince me that any of the type punning examples from the article, as given, are useful in production code, I will allow you that opportunity. But only after I've had the chance to make myself a bowl of popcorn large enough to share with whoever else might be entranced by the ensuing trainwreck.
And there are different version of 'dirty trick' perception too. Sometime what people perceive as 'dirty trick' is just normal use of the language while the muggles want to talk baby talk all the time.
For example, having to explain the functional use of 'x = !!variable' to a C programmer. There are many like that.
Sometime you just have to decide to raise the bar a little...
The reason I would prefer !! (or != 0) is that there are far too many err, non-bool bools. Even if you know for sure it's the stdbool.h bool, the next reader might not be aware -- presuming that when you do this you care about the distinction (otherwise I agree that the cast is fine).
It's not practical to make this a warning because of, e.g., dlsym (which is actually worse because void * and void (*)() aren't necessarily compatible types).
Another thing which makes this sort of thing difficult to warn in is the brokenness of autoconf. Autoconf checks that your system has memcpy (or anything that you check via AC_TRY_LINK) by compiling this program:
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.
Sometimes you just need to cast and assume that you've been given the right thing. Such as when going anywhere near a Windows message loop.
And sometimes you need to do terrifying, nonportable, ugly hacks in order to get something specific to work. My own contribution to this field is an ARM stack unwinder that requires only one __asm__ inline in order to get the link register and does everything else by interpreting instructions.
They are not many C compilers that are widely used in the wild. So, if I had to guess the average C programmer is using either GCC, Microsoft's C Compiler, or Clang.
If you write this in production code, and don't get sent home that day in tears, your company's code review process has failed.