Dark corners of C/C++: The typedef keyword doesn’t need to be the first word on the line

Here are some strange but legal declarations in C/C++:

int typedef a;
short unsigned typedef b;

By convention, the typedef keyword comes at the beginning of the line, but this is not actually required by the language. The above declarations are equivalent to

typedef int a;
typedef short unsigned b;

The C language (but not C++) also permits you to say typedef without actually defining a type!

typedef enum { c }; // legal in C, not C++

In the above case, the typedef is ignored, and it's the same as just declaring the enum the plain boring way.

enum { c };

Other weird things you can do with typedef in C:

typedef int;
typedef int short;

None of the above statements do anything, but they are technically legal in pre-C89 versions of the C language. They are just alternate manifestations of the quirk in the grammar that permits you to say typedef without actually defining a type. (In C89, this loophole was closed: Clause 6.7 Constraint 2 requires that "A declaration shall declare at least a declarator, a tag, or the members of an enumeration.")

That last example of typedef int short; is particularly misleading, since at first glance it sounds like it's redefining the short data type. But then you realize that int short and short int are equivalent, and this is just an empty declaration of the short int data type. It doesn't actually widen your shorts. If you need to widen your shorts, go see a tailor.¹

Note that just because it's legal doesn't mean it's recommended. You should probably stick to using typedef the way most people use it, unless you're looking to enter the IOCCC.

¹ The primary purpose of this article was to tell that one stupid joke. And it's not even my joke!

Comments (29)
  1. You made me double check the calendar – it isn't April 1st! O:-)

  2. dave says:

    Does pre-C89 C, which is to say pre-standardization C, really exist as a living programming language?

  3. alegr1 says:


    Current compilers still support many pre-standard behaviors (which you may wish go away). Such as old style function declarations, l-value cast, etc.

  4. Simon Buchan says:

    I think I'm at that point in learning C++ where everything I find out about what it can do (other than the new standards of course) is something horrible.

    Nitpicker's corner: Yes "You mean you started learning it today?" is a valid answer, thank you Mr. Hilarious.

  5. creaothceann says:


    #define "living programming language";

  6. Joshua says:

    @dave, alegrl: The Pre-ANSI C feature that is old-style function declarations is actually needed in a few obscure cases. E.g. what is the correct prototype of the open() system call? The third argument may be omitted altogether if the flags to the call (second argument) are such that no file can be created.

  7. Estix says:

    @Simon:  Wait until you see a code file with #include <setjmp.h> at the top :D

  8. Evan says:


    Indeed, the POSIX standard specifies the signature of open as "int open(const char *path, int oflag, … );". No need for K&R C.

  9. Mike Dunn says:


  10. mh says:

    Compile-time safety is for wimps – real men do it with inline asm.

  11. Gabe says:

    I would hope old-style function definitions don't go away. I work on a code base that still has #ifdefs for VMS and DOS (it was originally written for a VAX), so it is all in K&R C.

    If somebody decided the compiler should stop supporting K&R, we'd have to revise a million lines of code for no reason. Or, more likely, simply never upgrade to a newer compiler.

  12. Joshua says:


    Ever see this

    #ifdef _stdc

    #define _P(args) (args)


    #define _P(args)


    int fopen(_P(path, mode))

  13. Adam Rosenfield says:

    @Joshua: Right idea, but you can't have extra parentheses in function declarations, and you have to pass the correct number of arguments to the macro.  I've seen code that looks like this in order to support both function prototypes and old-style definitions:

    #ifdef STDC

    #define P(args) args


    #define P(args) ()


    int open P((path, mode))

    The extra parentheses in the macro invocation cause the whole parenthesized expression to get passed as a single argument to the macro.

  14. Paul Parks says:

    My first introduction to C was in high school from a programmer friend of mine. He explained macros as a way to define your own language within the language. At the time I thought that was the coolest thing I'd ever heard of. Now I see it as nearly pure evil.

    The heartbreaking thing about growing older is that you learn your lessons after you've done most of the damage you'll be remembered for. Like, creating your own language out of macros.

  15. JM says:

    In fact, in "typedef const volatile unsigned long int x", the first six words can appear in any order and it's still valid C, with the same meaning. Yes, "long typedef int const unsigned volatile x" is perfectly cromulent C, why wouldn't it be?

  16. Myria says:

    Another one that's not well-known: 2["meow"] == 'o'.  x[y] is defined as *(x + y), and the + operator for a pointer and scalar addition allows the scalar to be on either side.

  17. Joker_vD says:

    @Adam Rosenfield: Have you seen HTUtils.h from CERN httpd source code? It has contains crazy workarounds like the one you posted.

  18. j b says:


    Hmmm… You are saying that "volatile typedef … " is perfectly valid. I'd say it is perfectly confusing :-)

    But C is not the only one with "funny" grammatical properties. Good old Fortran doesn't have any reserved words at all. A real value may be named INTEGER, a subroutine may be called FUNCTION and so on. So


    may be a perfectly valid statemement. Spaces are also insignificant, so if your subroutine is named FUNCTIONX. and you have a real variable INTEGER5 and a complex variable REALCOST,


    is a perfectly valid call to this function.

    And finally, variables need not be declared: God is real, unless declared integer in an explicit og IMPLICT statement.

  19. SimonRev says:

    Myria, that may compile fine, but don't be surprised if it crashes because the compiler decided to put "meow" in a memory page that is read only (or in non-volatile memory in an embedded system)

  20. alegr1 says:


    No, it won't. ==

  21. Kaso says:

    @Joshua : At least on the code I have in front of me open() is implemented using va_arg, eg:

    int __open(const char*, int, int);

    int open(const char* pathname, int flags, …) {

       mode_t mode = 0;


       mode = va_arg(args,int);


       return __open(pathname, flag, mode);


  22. WPARAM Bao says:

    Windows widened its shorts going from 16 to 32 bit.

    [Short is still 16 bits even in Win32. -Raymond]
  23. The array stuff is always fun.

    A couple of times I have swapped the scalar and variable around (2[x] instead of x[2]), and then sit back and waited until someone came to me in a panic about the array being incorrect.

  24. JM says:

    Well as long as we're on the subject of operations that look dangerous but aren't: the Standard mandates that &(*p) is just the same as p without the pointer being dereferenced, meaning that stuff like

    int x[10];

    int *p = &x[10];

    is legal and well-defined since x[10] is the same thing as *(x + 10), so &x[10] is the same thing as x + 10, and forming a pointer to one past the end is OK (but, for heaven's sake, no further than that!). Of course, x[10] by itself is not OK since dereferencing such a pointer is undefined behavior.

    It's been ages since I've moonlighted as a C language lawyer, but I remember how much fun it was. In spite of (or likely because of) its wonky semantics, C has an excellent standard.

  25. cheong00 says:

    Thanks for the good laugh. :)

    Btw, I think the "typedef int short;" line is a *cough* good *cough* way for tricking future people who don't know this and following your code to introduce bugs into the system. And then we'll have one more entry to TDWTF.

  26. Medinoc says:

    @Estix: Wait, you mean in C++? Oh. My. Nonexistent. God.

    "A setjmp/longjmp call pair has undefined behavior if replacing the setjmp and longjmp by catch

    and throw would invoke any non-trivial destructors for any automatic objects"

    (C++11 n3290, book 18, chapter, 10, verse 4)

  27. Joshua says:

    @Medinoc: What's funny about that is the "undefined behavior" is essentially the same as calling free on a new'd object in an implementation where new calls malloc.

  28. Jake says:

    @Joshua – I dont think "undefined behavior" means what you think it means. Maybe in one compiler it does that, but it could do just about anything. I suggest everyone who programs in C/C++ read blog.regehr.org/…/213 and blog.llvm.org/…/what-every-c-programmer-should-know.html

  29. mikeb says:

    A little more than a year ago someone pointed me to this programming puzzle problem: http://www.spoj.com/…/HAJIME

    It drove me crazy figuring out the solution that doesn't rely on /**/ to replace spaces.  The fact that I'm posting about the puzzle here is obviously a clue to the non-comment solution.

Comments are closed.

Skip to main content