Path: utzoo!attcan!uunet!dino!sharkey!msuinfo!midway!mimsy!chris From: chris@mimsy.umd.edu (Chris Torek) Newsgroups: comp.lang.c Subject: Re: ANSI C prototypes Message-ID: <27462@mimsy.umd.edu> Date: 6 Nov 90 10:16:40 GMT References: <1005@christopher-robin.cs.bham.ac.uk> Organization: U of Maryland, Dept. of Computer Science, Coll. Pk., MD 20742 Lines: 192 >In article <1005@christopher-robin.cs.bham.ac.uk> ptf@cs.bham.ac.uk >(Paul Flinders ) writes: >>[my foo.h contains >> extern void ddprintf(const char *fmt, ...); >> and my foo.c contains >> void ddprintf(va_alist) >> va_dcl; >> and so I get an error]. The only solution that I've thought of is to >>define some preprocessor token in foo.c _before_ the include of foo.h >>and then #ifdef the extern declaration out. In article stanley@phoenix.com (John Stanley) provides a correct answer to a completely different question: > 1) Use a VAX. VMS C does not (at least, did not) care about the extern >missing from extern declarations. This means the same include that >defines a global int i may be included in all files. This refers to object declarations / definitions, as opposed to function declarations / definitions. VAX/VMS C uses the same model that most modern systems use, in which int foo; means: `There is an object called foo which is declared the same way in some other source file(s), possibly but not necessarily with ``extern'' added on the front.' (This is also known as `the common model' since it implements FORTRAN's named COMMON blocks.) Other systems, however, treat the above as both a declaration and a definition: `There is an object called foo. This is it. This is the only one.' On these systems writing `extern int foo;' means: `There is an object called foo. This is not it. Get me that object.' (This is also known as the `def/ref' model: a declaration without `extern' is a DEFinition, while one with `extern' is a REFerence, and there must be exactly one definition for everything.) The def/ref model is all that is required of a C system. Some find the common model more convenient; some find it more error-prone; since FORTRAN virtually requires it, most systems have it, but sometimes in a very limited form. John Stanley goes on to provide an incorrect answer (but one which, if interpreted in the context of the question answered above, becomes correct): > 2) Do what you thought of. It is pretty common, in my experience, that >the main routine has a #define MAIN, with the included files having: > > #ifndef MAIN > #define EXTERN > #else > #define EXTERN extern > #endif > >This way, all global variables get defined in the main routine, and >declared in the rest. This works fine for variables, but is not correct for function prototypes (more on this in a moment). >You probably could just #define extern to nothing, >but that would screw up any externs in any files follwing that one. Well, you can then `#undef extern'. A more likely problem is that many compilers refuse to allow `#define'ing anything that resembles a keyword (incorrectly, by ANSI X3.159-1989, since keywords do not exist during the preprocessing phase of the translation). Okay, so what is wrong with: extern void ddprintf(const char *fmt, ...); followed by void ddprintf(va_alist) va_dcl { ? The answer is in fact contained in the FAQ answers, but here it is again. EXCEPT IN RESTRICTED CIRCUMSTANCES, PROTOTYPE DECLARATIONS ARE NOT COMPATIBLE WITH OLD-STYLE DEFINITIONS. Note that both extern void ddprintf(const char *fmt, ...); and void ddprintf(const char *fmt, ...); are declarations. The word `extern' is ignored when declaring functions. The difference between a function declaration and a function definition is that a declaration ends with a balancing close parenthesis followed by a semicolon, while a definition ends with a balancing close parenthesis followed by an opening `{'. It is incorrect to put `extern' in front of a definition: `extern' is *not* ignored when DEFINING functions, only when DECLARING them. A prototype declaration tells the compiler: `This name is the name of a function. It has a fixed number of arguments, and each of these has a fixed type. The function returns a fixed type. Remember all of these facts.' If the declaration contains a `, ...' before the closing parenthesis, this changes to: `This name is the name of a function. It has a variable number of arguments, preceded by a fixed number of arguments. Each of the latter has a fixed type. The function returns a fixed type. Remember all of these facts.' When the compiler discovers a prototype DEFINITION, it treats that as another declaration (or the first declaration, if there have been no other declarations for the function) and makes sure the arguments match in number and types, and that the return type matches; it then compiles code for the function body. When the compiler discovers an old-style declaration or definition, however, this tells the compiler: `This name is the name of a function. It may or may not have some arguments. The arguments, if any, are not variable. The function returns a fixed type. Remember all of these facts.' The latter can sometimes, but by no means always, be reconciled with a prototype declaration that gave the same name and return type, but also gave the number and types of the arguments. It is not too difficult to define exactly when this can be done, although the result can be surprising. A: The arguments must be fixed, not variable. The ANSI standard permits compilers to use different calling sequences for fixed- and variable-argument functions, and therefore requires the programmer to announce variable-argument functions `in advance', as it were, using a prototype. All others are assumed to be fixed-arguments. B: None of the arguments in the prototype being reconciled against the definition may have a type which differs from its image under promotion. What B really means is that no argument can have type `char', `short', or `float' or any of their signed or unsigned variants. In other words, although int putc(int c, FILE *f); int putc(c, f) int c; FILE *f; { ... } /* ok */ is legal, the following is not: int put1(char c, FILE *f); int put1(c, f) char c; FILE *f; { ... } /* bad */ Since the image of `char' under promotion is `int', this fails rule B. Likewise, float mul(float, float); float mul(x, y) float x, y; { ... } /* bad */ is illegal, because the image of `float' under promotion is `double'. The reason for restriction B has to do with backwards compatibility (being able to run the mounds of code that use old-style definitions) without requiring systems to promote *all* arguments (being able to pass `float's around without turning them into `double's, etc). Any C system can *allow* the codes marked `bad' to work, but no C system is *required* to do so. So (after 162 lines), the answer is: declare your function as /* with optional `extern' included here */ extern void ddprintf(const char *fmt, ...); and define it as void ddprintf(const char *fmt, ...) { and any ANSI C system is obliged to compile it correctly. Do anything else and all bets are off. Since older C compilers do not recognize the `, ...' syntax, you may have to write: #ifdef HAVE_PROTOTYPE_DECLARATIONS void ddprintf(constchar *fmt, ...); #else void ddprintf(); #endif and #ifdef HAVE_PROTOTYPE_DEFINITIONS void ddprintf(const char *fmt, ...) { #else void ddprintf(va_alist) va_dcl { char *fmt; #endif va_alist ap; #ifdef HAVE_PROTYTYPE_DEFINITIONS va_start(ap, fmt); #else va_start(ap); fmt = va_arg(ap, char *); #endif ... -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 405 2750) Domain: chris@cs.umd.edu Path: uunet!mimsy!chris