1. Homepage of Dr. Zoltán Porkoláb
    1. Home
    2. Archive
  2. Teaching
    1. Timetable
    2. Multiparadigm programming (MSc)
    3. C programming (BSc for physicists)
    4. Project tools (BSc)
    5. Bolyai College
    6. C++ (for foreign studenst)
    7. Software technology lab
    8. BSc and MSc thesis
  3. Research
    1. Templight
    2. CodeChecker
    3. CodeCompass
    4. Projects
    5. Publications (up to 2011)
    6. PhD students
  4. Affiliations
    1. Dept. of Programming Languages and Compilers
    2. Ericsson Hungary Ltd

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