Fog Creek Software
Discussion Board




Typedef Insanity? (Not-so-newbie C question)

I'm sure every C programmer has seen this at one point. I believe parts of the Windows API do it. I've seen it recently in libpng, an open source library for de/encoding PNG files.

What I'm talking about: Say there's a type foo. Then some header file declares:

typedef foo *pfoo;
typedef pfoo *ppfoo;

So, what's the point? I argue that using these definitions makes code harder to read, because "foo **a;" looks like a pointer to a pointer (which it is), whereas "ppfoo a;" looks like an flat type (which it is not).

If one adopts the convention consistently, this is not so bad. But the meaning of "ppfoo" is still only revealed on second sight, because the p is part of an identifier, and I don't immediately associate syntactic meaning with letters in an identifier (unlike an operator).

I guess I can think of one use for "ppfoo" after all, which is that not-so-clever C programmers (who don't understand pointers) can pretend that they know what a piece of code does when they don't. And somehow, this smells a lot like "#define begin {".

Mack
Friday, August 27, 2004

10+ years from now we may have IDE which can display (automatically translate) these in the form the coder would like to see, but in the flat file it will use the original.


Friday, August 27, 2004

Talk about code clarity and talk about variables named 'a', quite funny :-)

Zero / One
Friday, August 27, 2004

Guilty.

Mack
Friday, August 27, 2004

I think the convention started so you could write:

ppfoo a, b, c;

and have them all be foo**

This is a common mistake:

foo** a, b, c; // only a is a foo**, b and c are foo

Also, it helps a lot to call it ppFoo instead, but I admit to not liking the convention either.

Lou Franco
Friday, August 27, 2004

I think this all started because of the need for mixed model programming (remember Win16 and NEAR and FAR pointers).

To call an API from a small model program you needed to use a FAR poiinter.  The only way you're going to get this right every time is to use a typedef.

Charles Petzold
Friday, August 27, 2004

Here's one:

Start with:
------------------------------
#typedef  pfoo  *foo;

//some code

foo bar;
pfoo bar_ptr;
bar_ptr = &bar;

//etc
-------------------------------

then write:

--------------------------------
template<class T> class smartptr<T>
{
    //some ptr class that handles delete/free() automatically,
    // and maintains reference counts
};
--------------------------------

then all you must do is replace:

#typedef pfoo *foo;

with:

#typedef pfoo smartptr<foo>;

and everything works just fine.

In general #typedefs allow for a zero-cost abstraction layer, zero-cost since they are handled by the preprocessor, and abstraction because they can be replaced at will by any class with the same public interface.

Devon Grey
Friday, August 27, 2004

C, for some reason, spawned a vast number of idiotic, obfuscational techniques aimed to make things 'better'. This is one of them, Hungarian notation is another.

Just don't repeat the sin in your own code.

Mr Jack
Friday, August 27, 2004

So can I hear the clear explanation to why hungarian notation is bad?  I am not trying to be dumb, I was born that way. :)

devinmoore.com
Friday, August 27, 2004

Oh wait, i got it.  It's not actually bad, it's just that people dont' want to go through and change variable names when a type changes.  I guess the proper way to do that would be to use property get/let or what have you to interface with the variables in a class, rather than trying to have public access to certain variables.  So it's not that hungarian notation is bad, it just allows one to condone certain other really bad things.

devinmoore.com
Friday, August 27, 2004

Devon Gray, interesting point. (Minor nitpick: Technically, typedefs are handled by the compiler, not by the preprocessor. That's why it's typedef, not #typedef.) It seems unlikely that changing the typedef is everything you have to do when switching to safe pointers, however. Some code might assume that pfoo and foo* are interchangable, for example.

Lou Franco, I agree. I want to add that for this reason some people suggest writing "type *variable" instead of the more natural "type* variable". IMO, that makes it easier to see what's going on.

To many others: Thanks for your comments, now I know that my thoughts are not completely off track. :-)

Mack
Friday, August 27, 2004

The last time someone asked why Hungarian was bad here was in February.  The time before that was last July.  Seems to be on a regular cycle.

Anyway, Hungarian is very useful but only if you use it as it was intended to be used, not as it actually IS used.  Confused?  Read this.

http://blogs.msdn.com/ericlippert/archive/2003/09/12/52989.aspx

Eric Lippert
Friday, August 27, 2004

-----------------------
#typedef pfoo *foo;

with:

#typedef pfoo smartptr<foo>;

and everything works just fine.
-----------------------

Hardly.  In one instance new and delete should be called, in the second, calling delete is probably not the right thing to do.

What is wrong with typedef?  It hides what what the type really is.  I have a really good tagging editor, and even with its power, to find out what pfoo really is will take 2x the amout of time, and force me to open several files to find out what the type is.

Here is a code snippet from a 3rd party.  Tell me how typedef has been used to keep the code base clean.  (I'm changing some names to protect my innocence).

typedef struct _XXX_DESCRIPTOR {
    tMYTYPE_U16        cbName;
    tMYTYPE_U16    *pwszName;
    tMYTYPE_U16        data_type;
    tMYTYPE_U32        cbValue;
} XXX_DESCRIPTOR;

Now take that an mutiply it by 10's of header files, 100's of typedefs.  That's what typedef in the modern world was become.

Charles P. (if that really is him) gave the only legitimate answer: to deal with FAR and NEAR.  And those days are *over*.

I see only a few useful usages of typedef:

1. For specifying exact bit lengths, which have to do with things like register IO:
typedef unsigned long uint32_t;
ONLY to be used when reading/writing registers of 32 bits in length or some thing from a network socket that is called out in an RFC as 32 bits long.

2. Dealing with C language callback types - although I still prefer not to use a typedef, I won't bitch about it when someone else does.

hoser
Friday, August 27, 2004

Typedef'ed pointers make writing function parameters a LOT clearer.

For example, if you've got a function that returns a pointer via an output parameter, you could write this:

int GetSomething( Foo **ppfoo );

Quick, what's ppfoo? What points to what? BZZZT! Times up!

Or, you could write this:

int GetSomething( FooPtr *pfp );

It's a bit more obvious that pfp points to a single thing (which it does) and you're not drowning in *'s.

I find it a lot more readable, anyway.

Chris Tavares
Friday, August 27, 2004

"Quick, what's ppfoo? What points to what? BZZZT! Times up!"

Quick, why use a pointer-to-pointer and not a reference-to-pointer?

Foo*&

BZZZT!  Time's up!

:)

indeed
Friday, August 27, 2004

-----------------------
#typedef pfoo *foo;

with:

#typedef pfoo smartptr<foo>;

and everything works just fine.
-----------------------

>Hardly.  In one instance new and delete should be called,
>in the second, calling delete is probably not the right thing to do.

That's assuming I'd be foolish enough to write a smart pointer without overloading new and delete. Wouldn't be a very smart pointer class at all, if it kept reference counts, and then ignored them.

>What is wrong with typedef?

[snip]

>That's what typedef in the modern world was become.

No argument here. I am, however, not convinced that tools ought to be blamed for the evil that is prepetuated by their users.

Devon Grey
Friday, August 27, 2004

Oops. I just noticed that I wrote  #typedef instead of typedef, treating it like a preprosessor directive (even saying it was one).

Silly me.

I guess this would be because I never, never use them. 

But being a theoretician rather than a developer, I have the luxury of not having to touch anybody else's code, or having anyone else trying to read mine.

It seems to me that typedefs used properly could make code much more readable and modifiable. Needless to say, without the "used properly" bit, horrible things could happen.

Devon Grey
Friday, August 27, 2004

From WinNT.h:

typedf void *HANDLE;

My code:

int PrintString (char *niceString)
{
  sprintf (globalString, "%s", niceString);
}

Later in my code:

  PrintString (fontHandle);

Compiler sais nothing, void* is good as any pointer, but
than I end up with a nasty bug. It happed to me today for the second time now with some handle and I hate it!

VPC
Friday, August 27, 2004

> Compiler sais nothing, void* is good as any pointer, but
than I end up with a nasty bug.

I'd recommend compiling your C code with the C++ compiler.

Assuming you're using MSVC, you can do this by renaming your *.c to *.cpp, or by adding "/Tp" to the list of compiler parameters in your project settings.

Because C++ is backward-compatible with C, any sufficiently-clean C code should compile with a C++ compiler. But C++ has stronger type-checking: it won't accept void* being passed to a function that expects a "char* parameter.

When you first make the change to using the C++ compiler, you'll probably get a lot of compiler errors due to the above ... which you fix by going through your code and adding casts, for example:

void* hello = "hello";
//the following won't compile
//PrintString(hello);
//so typecast because I assert that hello really is a string
PrintString((char*) hello);

Christopher Wells
Saturday, August 28, 2004

> I'd recommend compiling your C code with the C++ compiler.

Gee, that was quite an experience.

After some 500 errors I just had to stop this painfull
humiliation of my source code. But thanks, I'll use c++
compiler here and there for this kind of checking.

  for (i=a()+b(); i<a(); ) ...

is this really illegal in C++? And what part of it?

VPC
Saturday, August 28, 2004

Should have been:

i>a();

Where a() was GetTickCount() and here is the loop:

for (waitTicks = GetTickCount() + GetDblTime(); waitTicks > GetTickCount(); )

C++ Compiler gives error.

VPC
Saturday, August 28, 2004

> Gee, that was quite an experience. ... painfull humiliation of my source code.

I've been there!

> But thanks, I'll use c++ compiler here and there for this kind of checking.

You can also use a "Lint" program to do this, e.g. http://www.gimpel.com/ ... advantages of using Lint over the C++ compiler are that you can customize which types of error you want it to display, add comments to the code to tell it to ignore specific places, and ignore its messages.

Advantages of using the compiler are that it's free ... and, it lets you begin to use C++ instead of C (if you consider that an advantages, which you needn't).

> is "for (i=a()+b(); i<a(); ) ..." really illegal in C++? And what part of it?

The following compiles OK:

int a() { return 0; }
int b() { return 0; }

void main()
{
    int i;
    for (i=a()+b(); i<a(); )
    {
    }
}

The most likely error is that you haven't included (perhaps in a header file) the declarations of the a and b functions. The C++ compiler doesn't like to invoke functions whose definitions it hasn't seen: because, who knows, perhaps the a() function does exist somewhere but returns a double, or a short, or even a char*, rather than returning an int.

Christopher Wells
Saturday, August 28, 2004

Thanks for your time. i'm not a newbie - at least I like to
think so.

a() and b() are declared, as I specified in my later post and
in fact are Windows API calls:

for (waitTicks = GetTickCount() + GetDblTime(); waitTicks > GetTickCount(); )

In my first post I just tried to simplify things replacing these
functions with a() and b().

Heck, now even NULL is bad when I send it where (char *)
is expected. This is too much for me. For some reason, above
loop is not a problem any more. OK, I'll play with it later.
...
10 seconds later:

#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif

And I use in my project precompiled headers that are
compiled for C. That is why I have so many stupid errors.
Sorry for bothering you with unnecessary stuff.

VPC
Saturday, August 28, 2004

GetTickCount is a WINAPI function but I don't believe that GetDblTime is.

Christopher Wells
Sunday, August 29, 2004

Use a C compiler for C code, and switch on warnings (/W3 or /W4) for a Microsoft compiler.  Renaming as .cpp will give you too many errors for a first pass.


Sunday, August 29, 2004

You got me! Because it wasn't colored differently I assumed
it was from Win API. Now when I turned off C++ and when
the source compiled w/o errors, I see by its color it's a
macro.

#define  GetDblTime  GetDoubleClickTime

GetDblTime() is something from Mac OS API.

VPC
Sunday, August 29, 2004

<<That's assuming I'd be foolish enough to write a smart pointer without overloading new and delete. Wouldn't be a very smart pointer class at all, if it kept reference counts, and then ignored them.>>

I do believe it doesn't matter what you do with new and delete. operator delete/new must be overridden for the class a pointer to an instance of which you are trying to delete. So what happens is up to some other class, and not your smart pointer.

If you can call delete on a smart pointer, the problem is presumably the operator T * member function. So get rid of this. This does make the programming a pain in the arse, though.

Tom_
Sunday, August 29, 2004

Devon,

you mention you are a "theoretician" rather than a developer. forgive me but that sounds interesting, are you working in a university? or ?

best,
Synder

Synder
Sunday, August 29, 2004

*  Recent Topics

*  Fog Creek Home