Declarations and Definitions in C
January 18th, 2010
Please Note: This post is focusing on pre-C99. The reason being is that it is aimed at the embedded C programmer who tends to be working with pre-C99 based cross-compilers. Also I have split it into two as it became my larger, due to feedback, than first anticipated.
On the surface declarations and definitions in C are pretty straight-forward; but once we start introducing the concepts of scope, storage-duration, linkage and namespace life is not so simple.
Program Objects (Variables)
- if the statement has an “=” it’s a definition?
- otherwise, if it has “extern” and no “=” it’s a declaration?
- otherwise it’s a tentative-definition that may become a declaration or a actual-definition
Object Definitions
int ev = 20; /* definition – reserves enough memory to hold an int */
Object Declaration
Usage
When compiling a source file, a variable must be declared before it is used or it will result in a compiler error.
int main(void) { ev = 10; /* fails to compile as ev has not been declared */ return 0; }int ev = 20; /* definition – allocates 32-bits */
extern int ev; /* declaration – no memory reserved but defines sizeof(ev) */int main(void) { ev = 10; /* okay to use ev as declared, knows to read (say) 32-bits; k = 20 */ return 0; }int ev = 20; /* definition – memory reserved here and initialised */
int ev = 20; /* definition and implicit-declaration: reserves memory */int main(void) { ev = 10; /* okay to use ev as declared (implicitly) */ return 0; }
extern int ev; /* 1st declaration */
extern int ev; /* 2nd declaration */
int main(void) { ev = 10; /* okay to use ev as declared */ return 0; } int ev = 20; /* definition */
int ev = 20; /* actual definition */
int td; /* tentative definition */
int main(void) { ... return 0; }If an actual definition is found later in the source file, then the tentative definition just acts as a declaration. If the end of the source file is reached and no actual definition is found, then the tentative definition acts as an actual definition (and implicit declaration) with an initialisation of 0 (zero).
int ev; /* tentative definition becomes declaration */ int td; /* tentative definition become actual definition initialised to 0 */int main(void) { ... return 0; } int ev = 20; /* actual definition */
extern int ev = 20; /* actual-definition */
I’m sure someone can (and will) tell me why this is useful, but in my 25 years of doing C I’ve never had need to use it. I my view anyone found doing this should be made to sit in the corner wearing a hat with a big ‘D’ on it!
extern int(ev);
int(ev);
int(ev) = 20;
Functions
Function declarations and definitions are in many ways simpler than variables. A function definition includes the function’s body. e.g.
void f(int p) /* definition and implicit-declaration */ { ... }int main(void) { f(10); /* okay to call f as declared */ return 0; }
A function’s declaration (typically called its prototype) makes the compiler aware there is a valid function with this identifier. e.g.
void f(int p); /* declaration */int main(void) { f(10); /* okay to call f as declared */ return 0; } void f(int p) /* definition */ { // ... }
- the validity of the identifier
- the storage required to pass any parameters (by stack or register)
- the storage required for any return information
void f(int); /* declaration */ Before we move on, there are two problem areas we need to cover. First, let’s look at the following snippet:
int main() { f(20); /* call f with no declaration */ return 0; }void f(int i) /* definition and implicit-declaration */ { // ... }
Here we are trying to call a function that hasn’t been declared. As probably expected, this code fails to compile, but not for the reason you probably assume. Earlier I stated that an identifier must be declared before being used otherwise you get a compiler error. Unfortunately this only applies to variables and not functions!
f(20);
int f();
error: ‘f’ : redefinition; different basic types
If we change f’s return type to int, then this code will compile quite happily.
int main(void) { f(20); /* call f implicit-designator of int f() */ return 0; }int f(int i) /* definition’s designator matches implicit-designator */ { // ... }
If the return type is omitted, int is assumed.
This baggage is still evident today, as the following code should compile successfully:
int main() { f(20); /* call f implicit-designator of int f() */ return 0; }f(int i) /* definition’s designator has implicit return type of int */ { // ... }
warning: 'f' undefined; assuming extern returning int
Never ignore this warning. Some compilers (such as IAR) allow a non-standard extension requiring function prototypes. Note that C++ also requires prototypes, thus closing this loophole.
Can it get worse? Oh yes, much worse.
With a function definition, then empty parameter list is the same as void.
void f() /* definition and implicit-decln of void f(void) */ { // ... }int main() { f(20); /* error as call doesn’t match decln */ return 0; }
void f(); /* declaration */
void f(void); /* prototype-declaration – not the same as above */
void f(); /* declaration */
int main(void) { f(20); /* okay to call f as declared */ return 0; } void f(int i) /* definition */ { // ... }
void f(); /* declaration */
int main(void) { f(20); /* okay to call f as declared!!! */ return 0; } void f(void) /* definition */ { // ... }
Guideline: For all function always supply a function-prototype.
So hopefully that lays the groundwork of declarations and definitions we can now start addressing the concepts of scope, storage-duration, linkage and namespace.
Afternote:
void f() /* definition and implicit-decln of void f(void) */ { // ... }int main(void) { f(20); /* error a call doesn’t match decln */ return 0; }
