[ANU] [DCS] [COMP2100/2500] [Description] [Schedule] [Lectures] [Labs] [Homework] [Assignments] [COMP2500] [Assessment] [PSP] [Java] [Reading] [Help]
COMP2100/2500
Lecture 22: The C Programming Language IISummary
Multi-Module Programming in C
Outline
Functions
Static and automatic variables
Visibility of definitions
Linking modules into programs
Type-checking between modules
The C preprocessor
Functions
All executable code (instructions) in a C program must be inside a function. Procedures are also functions in C; they just happen to return a value to the caller, which can choose to ignore it if it wishes.
return-type function-name(parameter-list) { statements return expression; }Note:
Functions must be declared before use. This is very different from Java!
In C90, you can omit the type to mean int.
return expression returns the value of the expression to the caller.
Function Example
#include <stdio.h> int fibonacci(int n) { switch (n) { case 0: case 1: return 1; break; default: return fibonacci(n-1) + fibonacci(n-2); } } int main(void) { int i; for (i=0; i!=10; i++) { printf("%d\t%d\n", i, fibonacci(i)); } return 0; }
Function Prototypes
C prototypes resemble method signatures in the Java API documentation. They contain all the information a caller (client) needs in order to call the function.
(Well, actually they contain all the information the compiler and linker need in order to do their job correctly...)
If you can't define a function before using it, then declare it with a prototype.
In either case, the signature must appear before the function is called: either declare the function with a prototype and define it later, or define it.
A prototype is identical to the first line of the definition, except that after the parameters you put a semicolon.
return-type function-name(parameter-list);Prototype Example:
#include <stdio.h> int fibonacci(int n); int main(void) { int i; for (i=0; i!=10; i++) { printf("%d\t%d\n", i, fibonacci(i)); } return 0; } int fibonacci(int n) { switch (n) { case 0: case 1: return 1; break; default: return fibonacci(n-1) + fibonacci(n-2); } }
Modules in C
Large C programs are built from collections of files.
The variables and functions declared in a file may be private to that file, or visible outside it.
All externally visible identifiers share a common name space. In one program there may not be two or more externally visible things with the same name!
(Think what this does for reuse!)
(Richard's note: in fact there are multiple name spaces for different sorts of things. At this point in the discussion, we're keeping it simple.)
Automatic Allocation
By default, variables defined inside C functions have automatic allocation.
Storage is automatically allocated for such variables when the block they are defined in executes, and is deallocated when it finishes, just like locals in Java.
Example:
void reverse(char s[]) { int i, j; for (i=0,j=strlen(s)-1; i<j; i++,j--) { char c = s[i]; s[i] = s[j]; s[j] = c; } }Storage for s, i and j is allocated when reverse is called.
Storage for c is allocated each time the loop body is executed.
Static Allocation
Variables defined outside functions have static allocation. Storage is allocated for such objects statically when the code is compiled.
Example:
#define LINE_WIDTH 80 char line[LINE_WIDTH+1]; int getline(void) { ... } void putline(void) { ... }The functions getline() and putline() share access to a common array of characters line.
Static Allocation for local variables
Variables inside blocks may be declared static. Then they are also allocated when the code is compiled. In particular, they retain their values throughout the execution of the program.
Visibility of Definitions
Automatically allocated variables are visible only within the block where they are defined.
Example:
{ int i; /* visible in this block only */ ... }Statically allocated variables and functions are visible from their point of definitions to the end of the file, and by default, to code in other files!
Note: You can add the keyword static to top-level definitions (functions and global variables), and it makes them private to the file!
This situation, where words have different meanings in different contexts, can make life extremely confusing. It happens in Java and C++ too.
Example:
int j; /* visible from here on, and in other files. */ static int k; /* visible to the end of file only. */
Using Variables Defined in Other Files
In each file, you must declare the type of a variable before you use it.
You can use the keyword extern to declare the type of a variable without allocating any storage for it (defining it).
Example:
extern char line[];The variable line is an array of characters whose storage is statically allocated elsewhere.
The definition (and storage) for the variable could be later in this file, or in a different file:
char line[LINE_WIDTH+1];
Using Functions Defined in Other Files
In each file, you should declare the type of a function before you use it.
You use a function prototype to declare the type of a function without giving an implementation.
Example:
void reverse(char s[]);The definition of the function could be later in this file, or elsewhere.
void reverse(char s[]) { int i, j; ... }
Multi-Module Example
A program to read lines from the keyboard, reverse them, and print them to the screen.
Design:
A reusable module lineio for reading and writing lines. (Communicates though a global variable.)
char line[]
int getline(void) (Returns true if there is more input)
void putline(void)
The application-specific module revlines.
int main(void)
void reverse(char s[])
revlines.c
#include <string.h> extern char line[]; int getline(void); void putline(void); void reverse(char s[]); int main(void){ while (getline()) { reverse(line); putline(); } return 0; } void reverse(char s[]){ int i, j; char c; for (i=0,j=strlen(s)-1; i<j; i++,j--){ c = s[i]; s[i] = s[j]; s[j] = c; } }
lineio.c
#include <stdio.h> #define LINE_WIDTH 80 char line[LINE_WIDTH+1]; int getline(void){ int c, i; for (i=0; (i<LINEWIDTH) && ((c=getchar())!=EOF) && (c!='\n'); i++){ line[i]=c; } line[i]='\0'; return (c != EOF); } void putline(void){ int i; for (i=0; line[i]!='\0'; i++){ putchar(line[i]); } putchar('\n'); }
Linking Modules Into Programs
gcc -Wall -o program module_1.o ... module_n.oThe set of modules linked together must contain:
An externally visible function main.
For any identifier referenced but not declared in some module, there must be an externally visible definition of that identifier in another module.
No more than one externally visible identifier with any given name. (Richard's note: previous comment about name spaces applies here.)
Example:
gcc -c -Wall revlines.c gcc -c -Wall lineio.c gcc -Wall -o revline revlines.o lineio.o
The Example Linked
Type-Checking Between Modules (there is none)
The C compiler checks type consistency only within a file.
The linker doesn't know about types: if one object file needs something called x and another one supplies something called x, it marries them up!
Example:
typetest1.c
double x = 3.14159;typetest2.c
#include <stdio.h> extern int x; int main(void) { printf("%d\n",x); return 0; }Compile, link and run:
$ gcc -c -Wall typetest1.c $ gcc -c -Wall typetest2.c $ gcc -Wall -o typetest typetest1.o typetest2.o $ typetest -266631570
The C Preprocessor
All files are `preprocessed' before being given to the compiler proper. The preprocessor understands certain directives, including:
- /* Comment */
It removes all comments from the code; the compiler itself doesn't understand them!
- #define NAME value
Replace all subsequent instances of NAME by value. (This is how you define constants.)
- #include <file>
Copy in the contents of the file file from the standard library.
- #include "file"
Copy in the contents of user file file.
Ensuring Type Consistency
C programmers use the preprocessor and header files to help ensure type consistency in multi-module C programs.
For each source file foo.c:
Collect declarations of all the externally visible features of foo.c in a file called foo.h.
Add the line #include "foo.h" to the start of foo.c and every other file that uses features of foo.c.
This ensures that there will then be a consistent set of type definitions in each file, which the compiler can check.
Note: Put the variable and function declarations in the .h file, not the definitions.
The file foo.h is called a header file. It's a bit like the API documentation for a Java class produced with the javadoc tool, except that the programmer has to write it, rather than having a tool extract the information automatically.
(Richard's note: needless to say, there are tools that help you to do this.)
Multi-Module Example With Preprocessor
lineio.h
#define LINE_WIDTH 80 extern char line[]; extern int getline(void); extern void putline(void);revlines.c
#include <string.h> #include "lineio.h" void reverse(char s[]); int main(void) { ... } void reverse(char s[]) { ... }lineio.c
#include <stdio.h> #include "lineio.h" char line[LINE_WIDTH+1]; int getline(void) { ... } void putline(void) { ... }
[ANU] [DCS] [COMP2100/2500] [Description] [Schedule] [Lectures] [Labs] [Homework] [Assignments] [COMP2500] [Assessment] [PSP] [Java] [Reading] [Help]
Copyright © 2005, Jim Grundy & Ian Barnes & Richard Walker, The Australian National University
Version 2005.4, Monday, 2 May 2005, 14:32:00 +1000
Feedback & Queries to
comp2100@cs.anu.edu.au