1

With curiosity of the definition and scope of typedef I have written below C code in 2 .c files:

main.c

#include <stdio.h>

int main()
{
    int a = 5, b = 6;
    printf("a = %d, b = %d\n", a, b);
    swap(&a, &b);
    printf("a = %d, b = %d\n", a, b);
}

swap.c

typedef T;

void swap(T* a, T* b)
{
    T t = *a;
    *a = *b;
    *b = t;
}

To my surprise, the code files could be compiled with Visual Studio C compiler (cl.exe /Tc main.c swap.c)

And the program runs correctly! From my understanding, typedef requires 2 parameters, but why this code compiles and runs?

For further analysis, in the main function, I declared another 2 float variables and try also to swap both after swapping the 2 integers, but this time it fails to compile (with cl.exe). What amazing is the code could be compiled and run with Tiny C (tcc.exe main.c swap.c), so it works like a template method!

Iskar Jarak
  • 5,136
  • 4
  • 38
  • 60
Shuping
  • 5,388
  • 6
  • 43
  • 66

2 Answers2

5

Typedef is actually a declaration (it creates aliases for existing types), and is in no way limited to two 'parameters'. See Typedef Declarations (C) and Fundamental typedef operand syntax.

If you write typedef T;, you are declaring T to be an unspecified type (called "no specified type" in the C89 spec). It's a little (and only a very little, but conceptually this might help you) like #define X defines X but the preprocessor will replace it with the empty string (i.e. remove X).

So, you are typedefing T to be unspecified, which makes the arguments to your swap function of unspecified type.

What you are seeing here is that in C89 (but not C99, where it results in undefined behaviour - contrast ANSI 3.5.2 with ISOC99 6.7.2), unspecified types default to int, which is why your method works for integers but not floats in Visual Studio (presumably it disallows implicit integer typing by default). GCC will compile it with floats, though, provided you aren't using -Werror, which you probably should be.

I strongly suggest turning on some warnings: -Wall in gcc will spit out the following, among other things

swap.c:1:9: warning: type defaults to ‘int’ in declaration of ‘T’ [-Wimplicit-int]

The reason it "works" on floats is because floats and ints are probably the same size (32 bits) on your machine. Try it with double. Or char. Or short.

swap is really like this:

void swap(int *a, int *b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

with you calling

swap((int*)&a, (int*)&b);

Try it and compare the results for yourself.


Edit: I just tried it in tcc. -Wall does not alert you to the implicit int typing, sadly.

Community
  • 1
  • 1
Iskar Jarak
  • 5,136
  • 4
  • 38
  • 60
  • *Try it and see* is a dangerous philosophy that leads to undefined and unportable code. Perhaps you could use the C standard as a reference... More specifically, 6.3.2.1p2 says "If the lvalue has an incomplete type and does not have array type, the behavior is undefined." Hence the behaviour is not unspecified, but undefined. – autistic May 14 '13 at 04:39
  • I agree re. `try it and see` as a philosophy, but I meant it in the vein of `if you don't believe me, please observe the results before discussing it further`. However, re. undefined vs. unspecified types, I believe in C89 "no type specifier" is __not__ the same as undefined. "No type specifier" is treated as `int`, although I don't have the ISO publication to hand. FWIW Google tells me 3.5.2 of an ANSI-based '89 draft (http://flash-gordon.me.uk/ansi.c.txt) agrees (yeah, different numbering and not completely identical to ISO C89/C90, nor authoritative, I know). – Iskar Jarak May 14 '13 at 05:00
  • Oooh, I see. I was confusing it with forward declaration. In that case, would `typedef foo;` be a syntax error in C99 and C11? – autistic May 14 '13 at 05:08
  • Well, 6.7.2 of ISO C99 does not include _"no type specifier"_ in the list of type specifiers and also says _"at least one type specifier shall be given in the declaration specifiers in each declaration..."_, and violating _"shall"_ results in undefined behaviour (see section 4). GCC with `std=c99`, for instance, appears to have (haven't looked very deeply, don't quote me on it) implemented said undefined behaviour with C89 behaviour and complaining about implicit int typing by default (i.e. without -Wimplicit-int enabled). I suspect Visual Studio has implemented it as a syntax error. – Iskar Jarak May 14 '13 at 05:24
  • @IskarJarak thanks for your great answer! for learning purpose, sometime I use Tiny C compiler, with it I can swap 2 float numbers without loosing decimation. But from you explanation, the parameters are cast to (int*), it should loose decimation. why? or should i avoid using Tiny C compiler but use a more standard compiler? – Shuping May 14 '13 at 07:19
  • When you call `swap`, you are casting `float*` to `int*` (converting from one type to another). That is, from a _pointer to a float_ to a _pointer to a int_, rather than from a `float` to an `int`. An integer variable is just some bits that represent an integer when we interpret it that way. A float variable is just some bits that represent a number interpreted as a float, but the representation of an integer is very different to the representation of a float. – Iskar Jarak May 14 '13 at 10:14
  • When `swap` is executed, you are copying the bits that `a` points to and choosing to temporarily store them as an integer, before writing it to the location `b` points to. This is very different to reading them as a float, _casting_ that float to an integer, storing the result as an integer, and the writing it to the place `b` points to (the casting step would cause you to lose the decimal part, and trying to later read it in `main` as a float would not produce the result you expect because you would be reading an integer bit representation as a float. – Iskar Jarak May 14 '13 at 10:17
  • There's nothing wrong with using TCC for experimenting and learning things - it is a nice lightweight compiler for that sort of thing - but I think you would learn _more_ if you were to use a compiler with better options for warning flags. I'd recommend -Wall -Werror -ansi -pedantic for writing C89 with GCC, although it's hardly the only option. I don't know the equivalent flags for cl.exe off the top of my head, sorry. But you can learn a lot from reading compiler warnings! – Iskar Jarak May 14 '13 at 10:21
2

In C90 (which is what MSVC follows as a baseline when compiling C code), one of the possible type specifiers is (C90 6.5.2 "Type specifiers" - emphasis added):

  • int, signed, signed int, or no type specifiers

so if no type specifier is provided in a declaration (including a typedef) then the type defaults to int. this is commonly known as an "implicit int" declaration. Note that C99 removed support for implicit int (by default GCC only issues a warning for this when compiling in C99 mode).

Your typedef:

typedef T;

is equivalent to:

typedef int T;

So your swap() defintion is equivalent to:

void swap(int* a, int* b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

As it happens, when calling a function that hasn't been declared or prototyped (as occurs when you call swap() in main.c), the compiler will apply default argument promotions to arithmetic arguments and assume the function returns an int. Your call to swap() passes two arguments of type int*, so no promotion occurs (they're pointer arguments, not arithmetic). That happens to be exactly what the definition for swap() expects, so the function call works (and is well defined behavior).

Now, the calling code expects swap() to return an int since no declaration was seen, and your swap() function doesn't return anything (void). That is undefined behavior, but in this case there's no apparent problem (though it's still a bug in your code). However, if you change the definition of swap() so that it returns int:

int swap(int* a, int* b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

the undefined behavior goes away, even though swap() doesn't seem to return anything. Since nothing is done with the result at the call site, C90 permits the function to return with an expression. C90 allows this in order to support pre-standard code where there was no such thing as a void type.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760