52 comments

[ 2.7 ms ] story [ 49.8 ms ] thread
Fucken retard -- told you I made a compiler from scratch. Everything from scratch, all 138,000 lines.
Guess I grew up with old-school C. Looking at the question with example code made me think: "Am I missing something? Looks like pretty ordinary C to me. Why wouldn't it compile?"
Yeah, I definitely expected code with missing braces/brackets or something. This is pretty clear why it compiles.
If you were a programmer in the days before C had prototypes then you are definitely from the old school. If you're working on code that still looks like this in 2012 then maybe it's time to brush up on modern C. ;-)
He didn't say he still works on code like this, but like me, this program is obviously fine, there's no point in asking why it works because c is backward compatible and it's pretty clear to anyone who knows c that this will work. It doesn't mean we'd write code like that...
This is what bothers me about a lot of programmer communities these days. What is a very mundane detail of K&R legacy, that C programmers know very well either from looking at old source trees or making sense of error messages, suddenly turns into something where 116 people (current stack overflow +1 count) are totally amazed and breathless that they've discovered something deep. I suppose it's fair if they just don't know C, so they've not seen it and find it interesting, but could this person's dilemma not be resolved with a simple google search? Or even taking a guess?
It can be really hard to Google things when you don't know the terms involved. Until, someone posts source code and you get directed to stack overflow etc. Thus, the fact that out of millions of people with some familiarity with C 100 or 1000 chose to up vote an obvious post does not really seem that strange.
Thing is, the C standard (compared to many other standards) isn't even that complicated. Googling for the C standard doc, and then grepping through for "function declaration" would likely clear that up.

But it's way easier to consult the lazyweb than to do your own research.

Searching for "C standard doc" does not actually work.

"C99 standard doc" let's you find http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf but searching for "function declaration" does not take you anywhere useful.

That's not to say reading the whole thing is a waste of time, just beyond the scope of most new programmers.

Why do you assume 116 people are "amazed and breathless"? It's an interesting question and I don't believe the answer is obvious unless you've been exposed to legacy C code before, and such users may arguably constitute a large part of SO's audience.

You may be able to solve the question by a simple search, but it still fits in on StackOverflow. The fact that it was upvoted a lot is irrelevant to me, it just means a few people bothered to press a button.

Maybe "amazed and breathless" is a bit hyperbolic, but I assume the parent is referring to the number of people who had up-voted it at the time of his posting.

Perhaps for people who don't really know C, that's interesting information, but anyone who calls themselves a "C programmer" should not be surprised by that bit of code (well, you should be surprised if anyone still writes code like that, but not surprised that it's valid).

It's not even just an "exposed to legacy code" thing: C is a fairly simple language in both syntax and concepts (esp. when compared to most other languages people are using today), and I don't believe you can call yourself fluent in C without this kind of knowledge.

(I could also argue from the other angle: if you haven't been exposed to that sort of legacy C code, you probably haven't done it enough to call yourself fluent.)

I think it's an interesting question from the perspective of "what are some cool language gotchas that people not familiar with the language would find neat?"... or from the perspective of someone who is new to C, came across some code like that, and is trying to learn what's going on (and is apparently too lazy to google the C specification and spend 2 minutes reading it).

I agree with you. Maybe the better question is why does anyone think it shouldn't compile? And then we can ask how much programming history they know.

I think it's useful to know how C compilers, the C specification and C coding styles have evolved over the years. For one, it helps put some of the arguments for/against certain styles and syntax in perspective.

In short, make an effort to learn from history before you make the minimal effort required to learn from a contemporary forum like stackexchange.

Yeah :-) I didn't say I would -write- code like that. I only meant it looked like a lot of code I've run into over the years!
It's been a while since I've done this(usually by accident), but I believe that GCC and Visual Studio both throw up a warning along the lines of "Type not defined, assuming int".
I see questions online all the time that make me wonder just how old the compiler being used is. Modern versions of GCC, Clang and Visual Studio all have warnings enabled by default for an awful lot of valid, but probably wrong, code. Apparently people learning to code are somehow winding up on old compilers much more often than those of us who have been writing C and C++ for a long time.

Or, I guess, the people having problems learning to code are the people who happen to be on ancient compilers. Maybe it's not so surprising after all.

Compilers from the CD-ROMs in the back of ancient programming books from libraries maybe?
It might be the default settings on the compilers as well. I believe that in our environment, we set our warning level higher than the default, because a lot of the warnings(signed/unsigned mismatch, 32/64-bit pointers, etc.) can be an indication of an actual bug.
I think it is more likely something like this:

Many C programmers cannot make valid judgments as to the severity of a warning ('warning: bla bla may ...' => does it or doesn't it? I wouldn't know)

Also, those programmers typically would not know how to change their code to get rid of warnings. If they do, or if their compiler shoves a solution in their face (for example, a compiler might suggest to do "if((y=f(x)))") to get rid of an 'assignment inside if' warning), they do not see the advantage of the extra work.

The end result is code that produces hundreds of warnings. If a code change brings in a new one, people don't notice and/or don't care.

Some IDEs make things worse by allowing you to filter out warnings and messages from their output, so that you can focus on compiler errors. That is good for good programmers (they will fix errors, compile again, and then study every single warning), but bad for those who are happy to have code that compiles at all, as they will run into 'do not show me warnings' mode.

The first reaction to an unfamiliar warning in any language should be to consult the manual (or Google it). In the case of VisualStudio at least, this is trivial (these days) due to the warning numbers you can paste into Google and usually get not just an explanation, but a way to fix it (usually via SO).

Really, if a programmer can't fix warnings (and that includes taking the time to learn why they are there), what are you paying them for? My policy on projects I work on is to enable all warnings, treat them as errors, then only disable warnings after trying to fix them, researching them, and finally discussing them, and usually then only on a case by case basis (only occasionally do I eschew certain warnings altogether (such as -Waggregate-return for g++), and I leave them commented out with the reason they are disbled).

(Edit: a quick tip for gcc, BTW: use -fdiagnostic-show-option for easier search of a warning, and the name of it in case you have to pragma it out).

I've just tried it. That code compiles without warnings by the default configuration of gcc version 4.4.5 (Debian 4.4.5-8) in my desktop (it's an up-to-date Debian Stable install).

If the default settings of gcc included throuwing out warnings for every time a function is declared without parameters and missing the void, lots of people would complain, and loud. It's a very common construct.

Wow, that's crazy. I just tried it too (gcc 4.6.3). No warning options generate no warnings at all. -Wall only warns about the lack of return statement in main(). You only get the parameter-type default warning if you add -Wextra.

Interestingly, adding -std=c99 oddly removes the warning about the missing return statement in main().

I can't find a way to warn about the empty param list in the prototype, but it's possible that it's just not considered "bad enough style" to warrant one.

C++ is, of course, more strict. Try to compile that with g++, and you get several errors. The compiler needs to know how to mangle the symbol when it first encounters it, so you can't have "unspecified" parameter lists anywhere.

Me too. And I didn't realize that made me old, until scanning this thread. I guess time flies.
Hah, yeah. I looked at it and thought, "well, I'd kick someone for writing code like that nowadays, but looks perfectly valid to me."

(And I'm not even that old! I've only been writing C for 12 years or so!)

C++ programmers will be familiar with a related (and probably more commonplace) problem:

  struct Foo {int a;};

  int main(int argc, char **argv) {
      Foo foo();
      foo.a = 42;
      return 0;
  }
Looks ok, right? Wrong.

  error: request for member 'a' in 'foo', which is of non-class type 'Foo()'
foo()-with the parenthesis- is a function. foo-without the parenthesis-is a variable. That means that:

  Foo foo();
is a function pointer declaration within the scope of the main function. Since it's a function, which does not have member types, a is not within the scope of Foo().

Now, if there was a 0 argument constructor for struct Foo, this would be valid:

Foo foo = Foo();

Function declaration, not function pointer declaration. The latter would be

    Foo (*foo)();
this is pretty basic, I would hope no actual c programmers would ask this question. Compilers are fun aren't they, how bout this one:

  #include <stdio.h>
  #define yell(i,l,o,v,e,u) o##v##i##l
  #define billy_bob yell(i,n,m,a,t,e)

  billy_bob() 
  {
    printf("no main in da house!");
  }
Would you please explain the code?
(comment deleted)
>#include <stdio.h>

>#define yell(i,l,o,v,e,u) o##v##i##l

defines a macro called yell. The preprocessor operator ## concatenates it's operands together so something like ma##in gets transformed into main.

>#define billy_bob yell(i,n,m,a,t,e)

defines a macro called billy_bob that simply expands to the macro yell. When we call yell with the given arguments we get m##a##i##n (look at the order of the arguments for yell) which gets transformed into main.

>billy_bob()

Expanding the macro turns this line into main().

>{

> printf("no main in da house!");

>}

So now we've declared a main function that just prints "no main in da house!".

Hint: the 't' and 'e' in yell(i,n,m,a,t,e) are irrelevant.
There's the "##" operator with which you can glue together identifiers with the preprocessor.

  #define yell(i,l,o,v,e,u) o##v##i##l
  yell(i,l,o,v,e,u) will turn into the identifier ovil.
And billy_bot just uses the yell-macro to produce a "main" identifier.

  #define billy_bob yell(i,n,m,a,t,e)

  yell(i,l,o,v,e,u) --> o ## v ## i ## l
  yell(i,n,m,a,t,e) --> m ## a ## i ## n
So billy_bop is main.

Why is this features useful at all? When you actually want to programmatically create function names, type names, struct names in preprocessor generated code.

     #define ENCAPS_STRUCT(type,name)  struct encaps_##type { type name; }
     ENCAPS_STRUCT(int,a);
will generate...

     $ cc -E test.c
     # 1 "test.c"
     # 1 "<built-in>"
     # 1 "<command-line>"
     # 1 "test.c"

     struct encaps_int { int a; };
Ok what in the world does that do?
It's just a fancy way of assembling the identifier "main" without writing it explicitly. Inside a preprocessor macro definition, the double hash mark is an operator which concatenates its left and right operands. This is a lexical operation. The "yell" macro takes six arguments and returns the concatenation of arguments 2, 3, 0, and 1, ignoring 4 and 5.

The billy_bob macro calls the yell macro, passing in some letters - if you look at which letters from "i,n,m,a,t,e" end up attached to which arguments in "yell", you'll see that "yell' rearranges 'i,n,m,a' to spell the word "main".

I'm going to take a guess at this... the macros compile down to #define billy_bob main?

Let's see... yell() takes six 'bare' strings and appends four of them together in a certain order while ignoring the other two. This turns (i,n,m,a,t,e) into (m,a,i,n). The second define sets billy_bob as the output of yell.

I don't know C very well, is this correct?

That's exactly right.
See, this is why I like HN. The discussion here is more interesting than the OP (which is, indeed, a very basic question that a little bit of research into the history of C would answer). I thought I knew about preprocessor magic (including stringification and variadic macros), but I'm glad to have run across this post (and the ensuing discussion) as I've learned something very useful from it (eg, how to construct more dynamic code in C/C++).
I think you mean preprocessors.
To be pedantic, that's just preprocessor abuse... nothing really to do with the compiler proper.
An empty parameter list in a C prototype means "parameters are unspecified".

In C++ it means no parameters.

To declare a C function parameterless you need to say int func(void).

Also, C will assume int if you leave off param or return types.

...I'm surprised I know that.

Is it a bad code, or otherwise?
I would say that it is ambiguous code.

The first C project that I worked on used this a lot. Its definitely not the easiest to understand when coming from a C++ background (because no parameter declaration means no parameters) or from other descendant languages.

It's actually not ambiguous code at all. It's perfectly well-formed and unambiguous with respect to the C standard.

What it is, however, is hard-to-read and -maintain code.

It's an older code, but it checks out.
It is a bad code.

It was quite ok.

This is another proof that as programmers we need to learn and improve constantly :)

I think it's a case of not having the compiler warning level set to it's highest level (which it should be) or the bad habit of some programmer's ignoring warning messages.
This is kind of an encouraging question. To me it screams "people don't know C anymore, capitalize on your skill while you still can".

    /* Pentium killer */
    long main = 0xc8c70ff0;
That's why I always tell people that gcc is mostly useless without --ansi --pedantic -Wall.