Deep C (2011)
pvv.org
pvv.org
> In a context requiring two function types to be compatible, they do not have compatible return types, or their parameters disagree in use of the ellipsis terminator or the number and type of parameters (after default argument promotion, when there is no parameter type list or when one type is specified by a function definition with an identifier list) (6.7.5.3).
> disagree in use of the ellipsis terminator
printf is called with the following implicit type signature (due to not including stdio.h)
int (char *, int);
However, the actual printf signature is as follows. int (const char *, ...);
While char * can be used where const char * is being used, int is not a valid replacement for ellipsis terminator. As such, the correct answer is that it's undefined behaviour.(of course, most implementations will allow this code, but strictly speaking as a language lawyer it is undefined behaviour)
What might be useful though is something like -wall printing all flags representing current best practices, so you can have a snapshot of it when writing new code (the problem with current -wall is again potentially arbitrary changes on compiler updates as new warnings get added, if it actually did warn on everything)
Then you can choose when you want to update to whatever modern best practices
I've been the lead on a fairly large Fortran project, and we make a point to run our test suite on as many compilers as possible, with the strictest flags to avoid any kind of "undefined" behavior (which is rarer in Fortran than in C). I believe it's the only way to ensure long term maintainability.
1. load x into a register
2. spill the register holding x to the stack
3. call f
4. reload the "local" version of x from the stack
5. add
On the other hand, if the order of evaluation can be chosen by the compiler, it can do this:
1. call f
2. load x into a register
3. add
(Details depend on whether the caller or the callee saves x, but you get the idea.)
I think this is done early, during lowering to a "mid-end" intermediate representation (whether SSA or not). This is the best time to do it, since the legality of this reordering is a property of the source language, which you want to forget about in the mid-end. On the other hand, in this particular example (function call vs. global variable access), I don't think there is any reason to delay this; doing the call first should never be worse than loading the global first.
As for deciding other things later, instruction scheduling and global code motion can reorder a lot of things, though calls and memory accesses block many movements.
What is this hoping to accomplish? To convince candidates that they can't print a number? Maybe instead of intimidating people they should be inspiring them?
Edit: oh -- and (this I find truly revolting) there are companies that have not updated their coding standards and mandate that all written code should target an older standard (usually C89).
That being said... It's still solid and I think the slides have many, many important points, both for developer attitudes and things to know about C and C++. A lot of these obscure quirks can hit you hard if you're not paying enough attention. The order of initializer lists is extremely painful and hits me every so often even though I would probably catch it in an interview.
If anyone takes anything away from this w.r.t. C and C++, I'd say the best thing would just be to always code with `-Werror -Weffc++ -Wall ...`
Personally I don't care about the tiniest details too much. I pick them up as I go and typically at least remember that there was something that I can look up again later. Also it's really easy to navigate around most pitfalls. As the first programmer says, "I would never write code like that".
The best quote that resonates with me, has to come from an unattributed UNIX fortune file:
> Speaking as someone who has delved into the intricacies of PL/I, I am sure that only Real Men could have written such a machine-hogging, cycle-grabbing, all-encompassing monster. Allocate an array and free the middle third? Sure! Why not? Multiply a character string times a bit string and assign the result to a float decimal? Go ahead! Free a controlled variable procedure parameter and reallocate it before passing it back? Overlay three different types of variable on the same memory location? Anything you say! Write a recursive macro? Well, no, but Real Men use rescan. How would a language so obviously designed and written by Real Men not be intended for Real Man use?
C, with it's almost-assembly and fairly predictable semantics was an absolute blessing.
If I'm in a situation where I need to make a deep copy of an object, I need to ask myself why am I making an exact copy of a complex object with lots of internal pointers and state, an expensive operation. If I'm going to modify the result of the copy, perhaps what I should do is write a function that generates a new, different object based on the original one, in which case I would be passing in a const reference to the old object and maybe some other data about the new object I want to create. And if I'm not modifying the object, again why not just pass a const reference? Also, the exact behavior of all these hidden functions can be really hard to figure out. Who wants to try and figure out how many times the copy and assignment operator is called if you do a=f(g(a)); and pass by value? No thanks.
It can be useful, though. Many years ago, we used it to trim the twenty bytes or so that prevented an updated version of our firmware from fitting into the tiny flash space of a device that had been on the market for quite some time.
It is at the very least useful to know it when debugging code written by programmers who thought this feature was nice and relied on it throughout the code (either because they thought it was a good optimization to make, or because it further obfuscated their code and thus contributed to the security of their jobs).
Linguists are still highly respectible people. Society needs them. Just that being a great author does not require you to be one.
Is there any good reason for writing "void" in empty parameter lists? I have never seen one and, unless there is one, that declaration is just useless line noise.
For main it doesn't matter much since usually you don't call it yourself, but consider:
int f(); // no arguments, apparently
int g(int a, int b, int c) {
return f(a) + f(b, c); // at least one of these is fishy...
}
int f(double d) { // oh, the caller guessed wrong. twice.
return (d != 0.0 ? 0 : 1);
}
GCC and Clang do not complain about this program even with -Wall (they do with -Wstrict-prototypes).Learnt something!
Either a brand new compiler or an existing compiler like gcc/clang that adds a new flag that performs its regular optimizations as long as they wouldn't break common assumptions about what their C code would do. Of course it would be hard to find these assumptions, but the linked presentation is a good start.
Personally if i was to do something like this i'd use a simple rule: what would be the dumbest, most straightforward way to implement a C compiler? What effect would some expression have in that compiler? Then this is what the "unsurprising" compiler mode should do - perform any optimizations as long as they do not interfere with that effect.
I think it would be a win/win situation for compilers to do that: they'd get to play their performance game and also provide a surprise-free mode without fully abandoning performance.
error C3873: '0x201c': this character is not allowed as a first character of an identifier
error C2065: '“': undeclared identifier
I think this presentation is supposed to convince you that it’s important to have a deep understanding of both the implementation details and official specification of your language of choice. However my take away is that C is really complicated and has no respect for the least astonishment principle.
# make your own (large) PDF from slideshare url
# image resolutions available: 320, 728 or 1024 pixels
# example: $0 http://de.slideshare.net/olvemaudal/deep-c 728 deepc.pdf
test $# -eq 3||exec echo usage: url $0 {320,728,1024} file.pdf
browser='curl -s -O';
#browser='ftp -4';
test ${#TMPDIR} -eq 0||cd $TMPDIR||exec echo set TMPDIR
$browser -o 1.htm $1;
echo downloading images... >&2;
tr '\40' '\12' < 1.htm \
|sed '/-'"$2"'\.jpg/!d;s/\.jpg.*/.jpg/;
s/.*https:/https:/;s/\".*//' \
|while read a;do $browser $a;done #slow step;
test -f *-1-$2.jpg ||exec echo download failed
pdfimage -o $3 *-$2.jpg;
exec rm *-$2.jpg 1.htm;
# pdfimage is sample program included with pdflib
# http://www.pdflib.com/You don't optimize early, you don't overthink. You can then add platform convention, indentation and other sugar to the base code to fulfill whatever workplace standard or best practice you need to match.
I bet most HN readers don't speak German.