Mixed prototyping considered harmful 
Author Message
 Mixed prototyping considered harmful

The FAQ should have said "mixed prototyping considered harmful"
instead of saying: "It is usually safe to mix the two styles (5.8)"
and "Doing so is perfectly legal (5.9)"

Consider this code:

extern void funky ();
int main() { funky(1); return 0; }
void funky(int t){t++;}  /* <--- */

This code is perfectly 'legal', it compiles silently using acc
(SC2.0.1) under SunOS 4.1.3, even alint has no warning at all.  As a
matter of fact, it compiles/lints without warnings on any platforms
I've tried.  Then, change the "int t" to "float t" and change 1 to
1.0, now what?  both acc and alint barf "identifier redeclared: funky"
(nothing else) and it's a fatal error.  Now, change to "char t"and
'1', both give the same "identifier redeclared: funky", but this time
it's a warning.  Again, change to "double t" and 0.0 --peace on earth
once more...hmm..problem with short types.  Of course, if use pure
ANSI prototyping, there would be absolutely no problem.

I learned this the hard way.  You know how long does it take to
isolate this problem from a project with several files and thousands
of lines with fairly complicated data types (eg.  pointer to struct
with pointer to pointer to struct...)?  I changed one of the args from
type int to float and suddenly the damn thing wouldn't compile.  To
make things more insidious, I happened to use a prototyping tool
called "cextract" with my makefile, I just did a "make proto" to
generate a header file like this:

#if __STDC__            /* older version of cextract use #ifdef here
#define PL_(x) x
#else
#define PL_(x) ( )
#endif /* __STDC__ */

extern void funky PL_(( float t ));

So I never worried about my prototypes.  Since the compiler complained
"identifier redeclared", I suspected some name conflict in my header
files.  I change the name of the function from the original getwell to
getsick and finally funky.  I spent a whole good afternoon today doing
things like: find . -name '*.[ch]' -print | xargs grep -n funky.
Finally, I did an acpp -P file_with_funky.c and found out this ANSI C
preprocessor does _not_ define __STDC__ to be 1, and failed the #if
__STDC__ test i.e. they become pre-ANSI prototypes.  The brain-dead
acc/alint/acpp has several levels of ANSI conformance.  The default is
ANSI plus Sun's compatiblity extensions which define __STDC__ to be 0!
Only option -Xc i.e.  maximum ANSI comformance define __STDC__ to be
1.  I still didn't realize at that point that mixed prototyping could
be a problem because alint didn't complain a thing when the arg is
type int or double, only barf the redeclaration thing and nothing else
with type float.  After much grief, I simplified/isolated the problem
to the above three lines, still getting no where.  Desperated, I then
tried different platform/compilers, same thing on a SC2000 running
Solaris 2.3, Finally came the DEC C compiler on an Alpha running OSF/1
1.3, and the message is crystally clear:

/usr/lib/cmplrs/cc/cfe: Error: funky.c, line 3: redeclaration of
'funky'; previous declaration at line 1 in file 'funky.c'
 funky(float t)
 ^
/usr/lib/cmplrs/cc/cfe: Error: funky.c, line 3: prototype and
non-prototype declaration found for funky, the type of this parameter
is not compatible with the type after applying default argument
promotion
 funky(float t)
 ------------^

Ditto to DEC on this one, beats the Sun's acc + alint hands down.
Don't ask why I didn't use Alphas in the first place...more horror
stories...  Then I suddenly saw the lukewarm fine print in Q5.8 of the
FAQ:

        It may also be safer to avoid "narrow" (char, short int, and
        float) function arguments and return types.

I've since changed the cextract to generate ANSI only (default off)
prototypes with no fancy macros.

Just as smoking is legal but harmful and that csh programming is legal
but considered harmful, I must say the C FAQ maintainer should add
"Mixed prototyping is legal but considered harmful".  Period.

--Luke

--
"Looking for a job..."  --Luke



Tue, 26 Nov 1996 13:26:17 GMT  
 Mixed prototyping considered harmful

Quote:

> extern void funky ();
> int main() { funky(1); return 0; }
> void funky(int t){t++;}  /* <--- */

> (...)  change the "int t" to "float t" and change 1 to 1.0, now what?

Now line 1 and 3 do not use the same call conventions, because
floats passed to unprototyped functions are promoted to doubles.
You see it easier if you mix the other way, then 'float' must be
prototyped as 'double':
        extern void funky (double);
        void funky (t) float t; {...}
Similar with char/short -> int.  This is the safest way to declare
functions with float/short/char args, if you really want that.

Quote:
> both acc and alint barf "identifier redeclared: funky" (nothing else)

Well, the warning does make sense in a way.  The second 'funky' is
incompatible with the first, so it must be another function, right? :-)

Quote:
> Now, change to "char t"and '1', both give the same "identifier
> redeclared: funky", but this time it's a warning.

Maybe chars are passed as words (ints) anyway, so funky() receives the
correct values even though the program is wrong.

Quote:
> I learned this the hard way.  You know how long does it take to
> isolate this problem from a project with several files and thousands
> of lines with fairly complicated data types (eg.  pointer to struct
> with pointer to pointer to struct...)?

Maybe 1 minute.  Since you got an error at a declaration, you could
use cpp to search for previous declarations of funky:
        cc -E badFile.c > badFile.i
        grep -nw funky badFile.i
It would be worse if funky was not declared at all before use, then it'd
be implicitly declared as int funky(), and you wouldn't even get a
warning unless funky happened to be defined in the same file.  That's
the reason for Q5.8.
--
Hallvard


Tue, 26 Nov 1996 15:45:35 GMT  
 Mixed prototyping considered harmful

: The FAQ should have said "mixed prototyping considered harmful" instead
: of saying: "It is usually safe to mix the two styles (5.8)" and "Doing
: so is perfectly legal (5.9)"

Perhaps.  But, indeed, it *is* usually safe, and doing so *is*
prefectly legal.  The simplest way to be safe via a simple rule would
be to consistently use one or the other style on a per-function declaration
basis.  Since pre-ansi style is deprecated, it seems best to always
use ANSI prototype style.

There are essentially two practical reasons to want to mix styles.
One is to support old code, which already has declarations in
the old style, in a program where newly added or imported code is
being or has been written in ANSI style.  The other is to ensure that
newly written code can be compiled with either ANSI or pre-ANSI
compilers.  In neither case is a mix of styles necessary between
the definition and declaration of the same function, which is the
part of all this that is dangerous.

The odd thing I noted was that using an optionally expanded macro
to write, eg:

:               #if __STDC__
:               #define PL_(x) x
:               #else
:               #define PL_(x) ( )
:               #endif
:               extern void funky PL_(( float t ));

doesn't seem to gain any good benefit.  It's new code, and so there's no
new-code/old-code problems.  And in the example, the definition was
prototyped, so no matter *what* you say about the declaration, the
definition couldn't be compiled unless you were using an ANSI compiler,
thus backcompatibility is not an issue.

Thus, the motive for such a macro in the context it appeared is not
clear to me.  It could be useful in writing include files to be used by
both an ANSI and a K&R compiler, but then the definition would have to
have two versions.

In writing for K&R/ANSI compiler backcompatibility, a larger set of
macros might be of some use:

    #if __STDC__
    #define PT(p) p
    #define PF(n,t)  (t)
    #define PP ,
    #else
    #define PT(p) ()
    #define PF(n,t) n t;
    #define PP ;
    #endif

    extern void funky PT((float f,short s));

    void funky PF((f,s), float f PP short s){
         [...]
    }

The point of this macro set is to change both the definition and the
declaration in a co-ordinated way.  Of course, they have their own
problems, such as the redundancy of giving the argument list and its
type separately.  I wouln't suggest using these macros, unless maximum
backportability is extremely important.  One could also use protoize and
unprotoize, but using these macros are perhaps somewhat more tool
independent.

--




Thu, 28 Nov 1996 06:56:19 GMT  
 
 [ 3 post ] 

 Relevant Pages 

1. Hungarian Notation Can be Considered Harmful (was Re: GCC/G++ vs Other guys)

2. cast considered harmful?

3. break considered harmful

4. strcat() considered harmful

5. Gotos considered harmful (Dijkstra vs. Knuth )

6. Optimization considered harmful

7. Optimization considered harmful

8. EOF considered harmful

9. bitfields considered harmful?

10. C Problem (or, GOTOs considered harmful)

11. Variable Initialisation Considered Harmful ?

12. GOTO considered harmful

 

 
Powered by phpBB® Forum Software