Sensible way to write function prototypes - c

I'm looking for a (clean) way of writing a function definition and a function prototype without code duplication. Since DRY is well established as a good idea and hand coding prototypes in header files is a clear violation this seems like a reasonable requirement.
The example code below indicates a (crude) way of solving the problem with the preprocessor. It seems unlikely to be optimal, but does appear to work correctly.
Using separate files and duplication:
foo.h:
#ifndef FOO_H
#define FOO_H
// Normal header file stuff
int dofoo(int a);
#endif /* FOO_H */
foo.c:
#include "foo.h"
int dofoo(int a) {
return a * 2;
}
Using the C preprocessor:
foo.h:
#ifndef FOO_H
#define FOO_H
// Normal header file stuff
#ifdef PROTOTYPE // if incorrect:
// No consequences for this test case, but we lose a sanity check
#error "PROTOTYPE set elsewhere, include mechanism will fall over"
#endif
#define PROTOTYPE // if incorrect:
// "error: redefinition of 'dofoo'" in clang & gcc,
// referring to int dofoo() line in foo.c
#include "foo.c"
#undef PROTOTYPE //if incorrect:
// No warnings, but should trigger the earlier #error statement if
// this method is used in more than one file
#endif /* FOO_H */
foo.c:
#include "foo.h"
int dofoo (int a)
#ifdef PROTOTYPE // if incorrect:
// "error: redefinition of 'dofoo'" in clang & gcc,
// referring to int dofoo() line in foo.c
;
#else
{
return a * 2;
}
#endif
The mechanism is a bit odd - the .h file doesn't conventionally include the .c file! The include guard halts the recursion. It compiles cleanly and looks reasonable when run through a standalone preprocessor. Otherwise though, embedding preprocessor conditionals throughout the source doesn't look great.
There are a couple of alternative approaches I can think of.
Don't worry about the code duplication
Change to a language which generates the interface automatically
Use a code generator (e.g. sqlite's makeheaders)
A code generator would work but seems overkill as a solution for a minor annoyance. Since C has been around for somewhere over 25 years at this point there's hopefully a community consensus on the best path to take.
Thank you for reading.
edit: Compiler warnings with gcc 4.8.2 and clang 5.1
Messing up the macro statements produces fairly coherent compiler error messages. Missing an #endif (easily done if the function definition is long) produces "error: unterminated #else" or "error: unterminated conditional directive", both referring to the #ifdef line.
Missing #else means the code is no longer valid C. gcc "error: expected identifier or '(' before '{' token" and clang adds "expected function body after function declarator". Both point to the correct line number, but neither suggest an #else is missing.
Spelling PROTOTYPE wrong produces coherent messages if the result is fatal and no warning if the result doesn't matter. The compiler warnings aren't quite as specific as they can be when definition and declaration differ, but they're probably specific enough.

The generally accepted path is your option 1), to not worry and just write the declaration twice.
The repetition coming from prototypes is only a small percentage compared to the function implementations. Macro hacks like in your question quickly become unwieldy and provide little gain. The macro machinery ends up being just as much code as the original prototypes, only that it's now much harder to understand what's going on and that you'll get more cryptic error messages. The trivial to understand duplication gets replaced by about the same amount of much harder to understand trickery.
With normal prototypes the compiler will issue warnings when things don't match up, with such a macro base solution you get hard to understand errors if you forget an #endif or something else doesn't match up. For example any mention of foo.c in an error might be with or without PROTOTYPE defined.

I would like to take a look at it from another point of view. As I like to see DRY principle, it is meaningful for the code that provides logic, not taking it as repeating strings literally.
This way it would not touch declarations, as they introduce no logic. When you see few pieces of code, that do (as in perform some task) the same, just arguments change, then it should be avoided/refactored.
And this is what you actually do. You just introduced some new pre-processing logic into code, i.e. #ifdef PROTOTYPE... #else ... #endif, that you will repeat over and over just changing the prototype and the body. If you could wrap it up into something that does not enforce to repeat the branch I'd say it is somewhat ok.
But currently you really do repeat some logic in code, just to eliminate a multiple declarations, which is basically harmless in the context you provide. If you forget something the compiler will tell you something is mismatched. It's c.
I'd say your proposed approach violates it more, than repeated declarations.

Related

Using macros with the same name in different header files

I use macros like #DEBUG to print some additional debugging info and even possibly do something differently to help me with debugging. For example:
in header a.h:
#define DEBUG 1
in src a.c:
#include "a.h"
int func_a () {
/*some code*/
#if DEBUG
//do this
#endif
}
What will happen if I use a macro with the same name in another file ?
header b.h
#define DEBUG 1
#if DEBUG
# define PRINT 1
#elif
#define PRINT 0
#endif
src b.c
#include "a.h"
#include "b.h"
int func_b () {
/*some code*/
#if PRINT
//do this
#endif
/*some code*/
#if DEBUG
//do this
#endif
}
What will happen if I change the value of #DEBUG in one of the headers? I saw in some other answers that redefining a macro is not allowed in the C standard. But when I compile with GCC using the -Wall flag I see no errors or warnings.
What will happen if I use a macro with the same name in another file ?
It depends. C does not allow an identifier that is already defined as a macro name at some point in a translation unit to be defined again at that point, unless the redefinition specifies an identical replacement list. This is a language constraint, so conforming implementations will emit a diagnostic about violations they perceive. Compilers may reject code that contains violations, and if they nevertheless accept such code then the resulting behavior is undefined as far as C is concerned.
In practice, implementations that do accept such violations have two reasonable choices (and a universe of unreasonable ones):
ignore the redefinition, or
process the redefinition as if it were proceeded by an #undefine directive specifying the affected macro name.
Implementations of which I am aware accept such redefinitions and implement the latter option, at least by default.
If your headers are defining macros solely for their own internal use then you may be able to address the issue by exercising some discipline:
Each header puts all its #include directives at the beginning, before any definition of the possibly-conflicting macro(s).
Each header #undefines the possibly-conflicting macro at the end, under all conditional-compilation scenarios in which the macro may be defined in the first place.
On the other hand, if the macro is intended to be referenced by files that use the header(s) where it is defined then undefining it within the header would defeat the purpose. Under some circumstances, probably including yours, you can address that by defining the macro only conditionally in each header:
#if !defined(DEBUG)
#define DEBUG 1
#endif
That will avoid redefinition, instead using (only) the first definition encountered, which may even come from compiler command-line arguments. If you do this, however, it is essential that all the default definitions specified in that way be the same, else changing your headers' inclusion order will have unexpected effects code that depends on which definition is used.

Is there a tool that checks what predefined macros a C file depends on?

To avoid impossible situation one could reduce the problem to two cases.
Case 1
The first (simplest) case is situation where the preprocessor has a chance to detect it, that is there's a preprocessor directive that depends on a macro being predefined (that is defined before the first line of input) or not. For example:
#ifdef FOO
#define BAR 42
#else
#define BAR 43
#endif
depends on FOO being predefined or not. However the file
#undef FOO
#ifdef FOO
#define BAR 42
#endif
does not. A harder case would be to detect if the dependency actually does matter, which it doesn't in the above cases (as neither FOO or BAR affects the output).
Case 2
The second (harder) case is where successful compilation depends on predefined macros:
INLINE int fubar(void) {
return 42;
}
which is perfectly fine as far as the preprocessor is concerned whether or not ENTRY_POINT is predefined, but unless INLINE is carefully defined that code won't compile. Similarily we could in this case it might be possible to exclude cases where the output isn't affected, but I can't find an example of that. The complication here is that in the example:
int fubar(void) {
return 42;
}
the fubar being predefined can alter the successful compilation of this, so one would probably need to restrict it to cases where a symbol need to be predefined in order to compile successfully.
I guess such a tool would be something similar to a preprocessor (and C parser in the second case). The question is if there is such a tool? Or is there a tool that only handles the first case? Or none at all?
In C everything can be (re)defined, so there is no way to know in advance what is intended to be (re)defined. Usually some naming conventions helps us to figure out what is meant to be a macro (like upper-case). Therefore it is not possible to have such tool. Of course if you assume that the compilation errors are caused by missing macro definitions then you can use them to analyze what is missing.

Preventing incorrect macro expansion in gcc

Is there any way to prevent gcc from expanding a macro in this:
#define putc(a) fputc(a)
...
void _putc(char ch) {}
struct foo { void *(putc)(char ch); }
struct foo f = {_putc;}
(&f)->putc('X'); // this is an error because it gets expanded into fputc, which is very inappropriate.
I don't want to use #undef putc because it messes up other things.
Including <stdio.h> may or may not define macro functions. In either case, a real function is provided.
It's probably not the best idea to name a function pointer like a standard library function, but you can do it. To prevent macro expansion, you have basically three options:
#undef it. You said this would mess up other things, though this shouldn't be a problem -- a real function with that name still exists. For some functions, you may miss optimizations or warnings (for functions like printf, for example), however (depending on your compiler).
Don't include the header file and declare the function yourself. I mention this for sake of completeness rather than as a real suggestion. This doesn't work if you need a type definition provided only in the header you don't want to include.
Don't put an opening parenthesis after the macro name, as in
((&f)->putc)('X'); // or (f.putc)('X'); -- looking less confusing.

In C I am getting an "error: expected expression before ')' token" on compile

I'm guessing that this is going to be a really obvious and simple solution.
int is_full() {
return (top == STACK_SIZE);
}
The issue could be that 'STACK_SIZE' is a macro declared on compilation simply by entering -DSTACK_SIZE=10. That 10 can be something else. I'm unsure how to handle that, or if I'm declaring it right, or need to declare it inside the program as well.
You think STACK_SIZE is being defined, but its not.
the compiler sees:
int is_full() {
return (top == );
}
figure out why the #define is not going through.
I think you are using a macro from a Makefile. I assume this looking at the -D in front of STACK_SIZE=>
-DSTACK_SIZE=10. I assume also that you are using something like gcc because Visual Studio for example uses \D to define macros. This way to define the Macro as you can see above is not portable.
The macro can be defined also in the code. In this case you need to write
#define STACK_SIZE (10)
without using the '=' symbol. This method is portable, by the way the first form is often preferred when you have to change value depending on configurations as it doesn't require to change the code. In any case the code you wrote cannot be compiled if
-DSTACK_SIZE=10 isn't properly declared in the Makefile so, if you choose to use the Makefile definition, you need to protect your code by the weird errors you posted doing something like this:
#ifndef STACK_SIZE
/* handle with a default value o with the macro #error */
#else
return (top == STACK_SIZE);
#endif
Obviously in your case you made some mistake on the Makefile, infact the STACK_SIZE is not defined and you get that return(==); error that you could simply avoid (or manage) with the instructions shown above.

Preprocessor on C

I added this in my code:
#ifdef DEBUG_MODE
printf("i=%d\n",i);
fflush(stdout);
#endif
and my question is, if I'm not in DEBUG_MODE what the compiler does when compiling this?
The compiler will do nothing, because there will be nothing there when DEBUG_MODE is not defined.
#ifdef and #endif control conditional compilation. This happens during an initial pass over the program, making dumb textual substitutions before the compiler even begins to consider the file to contain C code specifically. In this case, without the symbol defined only whitespace is left. The text is never even lexed into C tokens if the preprocessor define tested for isn't defined at that point.
You can see this for yourself: just invoke your compiler with whatever flag it uses to stop after preprocessing - e.g. gcc -E x.cc - and at that point in the output there will just be an empty line or two. This is also a very important technique for understanding macros, and a good thing to do when you just can't guess why some program's not working the way you expect - the compiler says some class or function doesn't exist and you've included its header - look at the preprocessed output to know what your compiler is really dealing with.
if DEBUG_MODE is not defined, the code under it will not be compiled.

Resources