C Craft
crypto.stanford.edu
crypto.stanford.edu
struct {
int n;
char c[0];
} *foo = malloc(16); /* sizeof(*foo) probably 4, so 12 bytes follow */
foo->n = 16;
for(int i = 0; i < 16; i++) { /* puts 16 bytes after foo->n. oops. */
foo->c[i] = 'a' + i;
}
Not that I don't love C for terseness and power or appreciate the systems one can build in it, but seriously. struct thing {
int n;
char c[0];
};
int i_need = 16;
struct thing * foo = malloc(sizeof(struct thing)+i_need);
foo->n = i_need;
for (int i = 0; i < foo->n; i++ ) {
foo->c[i] = 'a' + i;
}
And if your compiler won't let you do char c[0] you can do char c[1] and either blow a byte, or do the calculation correctly. Note that you have to be careful with this sort of allocation if you are on a machine that has certain alignment requirements (e.g. Sun). struct thing *foo = malloc(sizeof(struct thing) + sizeof(char) * i_need);
since sizeof(char) isn't guaranteed to be 1 byte on all systems.Basically, whenever he says "never" do something he means he doesn't like to do it. For example, his answer to not having a multi-level function break is a nested helper function. God no. That's one of the few times when it's correct to use goto.
Edit: Ahh, he also advocated always using mmap instead of the standard file IO functions. Wow that's bad.
I usually just use standard IO, unless I have a large file that might be randomly accessed by multiple processes. In that case I'll use a mmap. But, I don't code in C very much anymore, so I was wondering how much of a difference there really is.
Basically, there's a reason why it's called "standard" IO. Generally you shouldn't use system calls unless you are absolutely, 100% sure that your code will only ever need to work on one system.
mmap is (allegedly) portable across Unixes:
http://opengroup.org/onlinepubs/007908775/xsh/mmap.html
and you can do the same thing (with a few more hoops) on Windows.
This of course depends on your OS (and version). My advice would be to pick API based on “semantics” (or need for portability). If you need to read through a file, use the API intended for that because mmap will not be optimized for that access pattern, but stdio will.
However, the criticism of JVM seems like it's written in 1999 about the Blackdown JVM (Josh Bloch in his interview in Coders At Work attributes much distrust of Java earlier on at Google to experience with this platform). Hot-spot can, many times, be within 10-15% of C's performance. In many cases code running on the JVM will outperform C++'s runtime (and particularly boost).
That's not to say there aren't issues: garbage collection pauses, the stack based nature -- rightly -- is pointed out as an impediment, but it's odd that LLVM isn't mention. However, excluding JVM due to performance (especially when used with modern languages, e.g. Scala, Clojure, Ruby, Javascript) seems short-sighted. See Cliff Click's post on JVM performance:
http://blogs.azulsystems.com/cliff/2009/09/java-vs-c-perform...
Repeat after me: HotSpot is not Blackdown, 1.5+ is not 1.2 (you have non-blocking I/O -- even though NIO is still painful to use, you have autoboxing, you have foreach, you have -- for all their limitations -- generics, you have annotations), you can run more than Java on the JVM. Even if you do choose Java (which has many flaws, not the least of them being a marketing-driven language, aimed at enterprise C++ developers, rather than a language made by hackers for themselves), you don't have to use Spring and J2EE. In most cases performance is a MacGuffin when it comes to Java and JVM.
I find I'm happiest working in a language that allows very easy integration with C, but provides garbage collection, some kind of module system, better strings, a REPL, etc. You have more flexibility while prototyping, but can rewrite the parts in C that need it, and all system calls (fork, dup2, etc.) are still accessible. Having a pervasive associative array ("dict" or "table") type in the language helps, too - it's an incredibly versatile data structure.
Lua works particularly well for this (and it's simple and clean, like C), though Python and Lisps/Schemes that compile to C work well too. (I can't vouch for Ruby here, since I already know Python and Lua and haven't bothered with it.)
See also: Andrew Koenig's "C Traps and Pitfalls" (http://www.literateprogramming.com/ctraps.pdf) and the book, which expands on the paper.
So basically if you want to create a large project in C you have to build a number of intermediate layers (otherwise the code will be a complete mess full of bugs and 10 times bigger than required).
This continue design exercise of creating additional layers is the hard part about C. You have to get very good at understanding when to write a function or not, when to create a layer of abstraction, and when it's worth to generalize or when it is an overkill.
Of course the same thing happens in other languages as well, and even in higher level languages, but usually in C there are more layers of abstractions required compared to a similarly complex project written in an higher level language.
Another difference with higher level languages is that with C the abstraction layers at the "bottom" usually have to deal with low level details that require some practice and knowledge. For instance it's common to implement data structures, some automatic memory management stuff like reference counting and so forth.
This is the reason why I think programmers should learn C and try to write at least a large project with it: it's a good exercise.
C just starts requiring these layers at a slightly earlier point in the abstraction continuum. On the other hand, the low-level abstractions are the easy ones: my experiences verify that in a medium-size project and up, with C you will have quickly built your own vocabulary and primitives and you get to the meat only a bit later than with some higher-level language.
I don't mean that C is all you need but that it's not the big problem in practice. I've seen so many large C projects where most of the code is about the problem domain itself and only a minor part is dedicated to overcome the C's lack of features and primitives.
I somehow recognize it as a good thing in C, forcing the programmer to build these layers early. You'll have to do that eventually and if you're so used to doing it already, you'll have more brain left for the actual problem itself.
http://crypto.stanford.edu/~blynn/c/ch03.html#_when_are_size...
Great C craft :-)
I think my high regard for C comes from work experience: the C projects I've worked on have been less of disastrous messes than the C++ projects I've worked on. It's hard to pinpoint why, because on a small scale C++ is very nice. But I've noticed that once > 10 people get involved, C++ projects become more bug-prone and difficult to understand and fix.
I especially love ADTs in C. C++ encourages you to declare public and private and protected class members in a single class declaration in a single header file. Yes, you can work around this (ie, have a single private member m_priv), but I haven't seen it done much in practice. In C, on the otherhand, it's much easier to separate implementation details from the public interface. Put "typedef struct Foo * FooHandle" in the public header and the structure definition in the .c file. This is common practice in C and very elegant. As a coworker once put it, "I don't like the way people put private members in header files in C++... it's like seeing the class's underwear".
"Most of my wishes for C have been granted."
That said, sometimes(I mean often) it does hurt when I have to work at a higher abstraction level in some fancier language and I keep thinking on what is going behind the scenes.
Your build system needs to support it. Your testing procedures need support it. In some cases the platform does not support it at all (Google App Engine).
But most importantly, how many Ruby/Python/PHP developers are able to write safe C code? How many of them would you trust to write server side C code that could bring down the system or introduce hard to find memory leaks?
Incidentally I wrote an article in defense of OO: http://www.zideck.com/blog/article.php?id=1
Then it would be a Twitter post, not a blog post.
> Well, not everything is an object. And I'm not talking about Java
> primitives, either, which should be objects. For instance, functions
> are not objects in most languages, and when they are it is confusing.
> See Javascript for further info on how this doesn't work.
....
I stopped reading here. It works very well.
(how the heck do we do preformatted text on HN?)
preformatted codeI recently have concluded the same thing. I suppose all programmers reach that conclusion at some stage in their education. However, in context its pretty entertaining: a programmer explaining why C is better -- because for him, certain benefits of C outweigh certain deficiencies -- while simultaneously pointing out that programming is the art of manipulating data, which is largely an abstraction that lies above the particular representations in language X (well, mostly).
That being said, I thought he did a good job. I would have been proud to write this page.
The `almost' is important. E.g. on the one hand you won't use any of Okasaki's beautiful purely functional data structures in a language without garbage collection like C. And on the other hand you will shy away from data structures that require mutation in a pure language like Haskell.
Also, he says that OO languages (really, only some) have weak support first-class functions and closures, since they place so much emphasis on objects.
His criticisms sound more accurate for Java and C++ than, say, Python or Smalltalk. (He mentions Eiffel specifically, but I have no experience with it.)
Also, reading code with several levels of inheritance (more than three, perhaps) means dealing with several levels of spaghetti code - the code becomes a mess of "this does that, but no, wait, it doesn't anymore here, though here it does that except it does this first."
Sometimes complexity is intractable. There are no silver bullets.
Whether a truth is universally recognized has no bearing on whether it is true, quite irrespective of what OO advocates believe or who/what they choose to blame. The big issue with C? It's tough to use correctly. The criticism that is being made against OO? It's tough to use correctly. Why does OO[1] just get to opt-out of the criticism by blaming the user, but C can't?
[1] Not even OO, but inheritance in particular.