The C programming language – Lecture 7.
Declarations and definitions
Declaration - tells the compiler what should it think about X.
- For variables: tells the compiler that the variable with a certain type exists (defined somewhere).
- For functions: tells the signature (return type and parameter list). The compiler knows whether conversion is required on parameter passing.
- For types: tells the compiler, that a type (struct, enum, etc. exists).
Definition - tells the compiler what is X in details.
- For variables: tells the type, the life and the scope. Allocate memory for that object.
- For functions: describe parameter list, return type and the algorithm to execute.
- For types: describe the data structure, and members.
The generic form of definitin/declaration is: storage-class type-name declarator-list
where declarator-list is: declarator , declarator …
storage-class is: auto, register, static, extern, typedef
Do not use auto and register. Auto has a different meaning in C++11 and register optimizations are done by modern compilers automatically.
The form of declarator is:
- function: decl ( param-list )
- pointer: * decl
- array: decl [ n ]
- otherwise: id
Definition
Every C elements should be defined exactly ones.
Functions should be defined outside of other functions; i.e. there are no inner functions. Variables can be defined outside (global variables) or inside (local variables) of functions.
Since C99 the place of a variable definition can be in anywhere, even between two executable statements:
1
2
3
4
5
6
7
8
9
10
void func()
{
printf("Hello");
int i = 0; // definition and initialization
while ( i < 10 )
{
//...
++i;
}
}
Variable and function definitions
1
2
3
4
5
6
7
8
9
10
int i; // integer variable
int *pi; // pointer to integer
int t[10]; // array of 10 integers
int func(void){...} // function without parameters, returning int
int func(){...} // function without parameters, returning int
int func(int i, double d){...} // same, i and d are formal parameters
void func(){...} // function without parameters, no return type
//...
char *getenv(const char *var){...}
These definitions can be used recursively
1
2
3
int **ppi; // pointer to a pointer to int
int tt[10][20]; // array of 10 arrays of 20 integers (200 int elements)
int *func(int){...} // int parameter function returning pointer to int
In case of disambiguity, usual precedence rules decide the meaning. We can use parenthesis to overrule precedence.
1
2
int *ptr_arr[10]; // array of 10 pointers to integers
int (*ptr_to)[10]; // pointer to an array of 10 integers
But there are a few exeptions:
- No array of functions (but there is array of function pointers).
- No functions returning functions (but can return with function pointer).
Pointers to functions should define full signature.
1
2
3
4
double (*funcptr)(double); // pointer to double f(double) func, like sin
//...
extern double sin(double); // declaration of sin, usually in <math.h>
funcptr = sin; // name of function is pointer to function value
Sometimes typedef is used to simplify definitions/declarations.
1
2
3
4
5
6
7
typedef double length_t; // length_t is synonym of double
length_t f(length_t){...} // f has a double parameter and returns double
//...
typedef double (*trigfp_t)(double);// trigfp_t is pointer to function
// signature is part of the type
trigfp_t inverse(trigfp_t){...} // function returning pointer to function
int trigfp_t[10]; // array of 10 pointer to double(double) function
Arrays with automatic life (local, non-static arrays) can be variadic, i.e. the size of an array can be a value of a variable.
1
2
3
4
5
6
7
8
9
static int t1[10]; // dimension must be known compile time
void f()
{
static int t2[10]; // dimension must be known compile time
int n;
// read n
int t3[n]; // ok
}
Declaration
Every C elements should be declared before the first usage. Multiply declarations are allowed.
1
2
3
4
5
6
7
8
9
10
11
extern int i; // integer variable is defined somewhere else
extern int *pi; // pointer to integer is defined somewhere else
extern int t[10]; // array of 10 integers
extern int t[]; // array of ? integers
extern int tt[10][20]; // array of 10x20 integers
extern int tt[][20]; // ?x20 integers, only leftmost dim can be omitted
extern int func(void); // function without parameters, returning int
int func(void); // same, compiler understands: this is declaration
static int func(); // func has no external linkage, parameters unknown
//...
/* extern */ char *getenv();
Initialization
Variables can be initialized when defined. Actually, the best strategy is to delay definitions until we use and initialize the variable.
1
2
3
4
5
6
7
8
9
10
11
12
int i = 1;
double pi = 3.14;
extern double sin(double); // declaration
double (*funcptr)(double) = sin;
int arr1[10] = {0,1,2,3,4,5,6,7,8,9}; // ok, but hard to maintain
int arr2[10] = {0,1,2,3,4,5,6,7,8}; // arr2[9] == 0
int arr3[] = {0,1,2,3,4,5,6,7,8}; // int arr3[9]
char str1[] = {'H','e','l','l','o','\0'}; // char str1[6]
char str2[] = "Hello"; // char str2[6]
Pitfalls
1
2
3
int i, j; // i and j are integers
int* ip, jp; // ip is pointer, but jp is integer
int *ip, *jp; // ok, both ip and jp are pointers