CPS 250, 346, &
444/544 Lecture notes: Compiling C in UNIX
Coverage: [UPE] §6.6 (pp. 187-190)
and [USP] Appendices A.2 (pp. 800-807) and A.4 (pp. 809-812)
Overview
- static vs. dynamic linking
- macros
- conditional compilation
- error handling
- debugging
Header files vs. libraries
- header files contain prototypes
for functions defined in libraries
- while function definitions are pre-compiled in libraries,
libraries must be linked
- to include
- system header files, use <filename.h>
(e.g., #include <stdio.h>;
same as #include "/usr/include/stdio.h")
- user-defined header files, use (double quotes)
"filename.h"
(e.g., #include "myheader.h")
Standard C library
- consists of the functions prototyped in
<ctype.h>, <stdio.h>, <string.h>,
<stdlib.h> and others
- functions in header files given above are automatically linked
- other libraries must be explicitly linked using -l option to
gcc
(e.g., gcc -lm ... (to link the math library))
Compiling a C program in UNIX
- use gcc (gNU c compiler;
GNU = Gnu is Not Unix)
- gcc ptrEx1.c (compiles and links;
produces executable a.out)
- gcc -o ptrEx1 ptrEx1.c (produces executable ptrEx1)
Compiling
GNU C and C++ Compiler
- to compile a C program use gcc
- to compile a C++ program use g++
- examples:
$ gcc -c parser.c
$ gcc -g -c parser.c
compiles, but does not link, parser.c;
produces object file parser.o
$ gcc parser.c
compiles and links parser.c;
produces executable a.out
$ gcc parser.c main.o
compiles and links parser.c with main.o;
produces executable a.out
$ gcc -o parse parser.c main.o
compiles and links parser.c with main.o;
produces executable parse
C compilation steps using gcc
- gcc -E pgm.c
- see stdio.h?
- writes (expanded C source code) to stdout
- gcc -S pgm.c:
writes (assembly language code) to pgm.s
- gcc -c pgm.c:
writes (object code) to pgm.o
- gcc pgm.o:
links and writes (executable) to a.out
- ar t /usr/lib64/libc.a | grep '^printf.o' (culls out the
object code for printf)
gcc options graphically
C compilation steps graphically
Another view of the C compilation steps
(ref. O'Reilly)
file command
- determines file type
- syntax: file <filename(s)>
- pretty sophisticated, does not just determine file
type based on the file extension
- for instance,
$ file textfile.txt
textfile.txt: ascii text
$ file cat.c
cat.c: c program text
$ mv textfile.txt textfile.c
$ file textfile.c
textfile.c: ascii text $ file cat.i
cat.i: ascii text
$ mv cat.i mycat.c
$ file mycat.c
mycat.c: ascii text
$ file cat.s
cat.s: assembler program text
$ file cat.o
cat.o: ELF 32-bit MSB relocatable SPARC Version 1
$ file a.out
a.out: ELF 32-bit MSB executable SPARC Version 1,
dynamically linked, not stripped, no debugging information available
More on compiling with gcc
- gcc -I
<directory to add to search path for include files>;
searched before the standard system include directories
- gcc -l<abbrev>
- explicitly links library corresponding to
the library with the abbreviation following the -l
- for instance,
gcc -lm log10.c (link against the math library)
- typically only C standard library linked by default
- gcc -L <directory to add to search path for libraries>;
those searched for -l
- g++ (GNU C++ compiler) works the same way
Static vs. dynamic linking
- static linking
- compiles slower
- larger executable
- faster executable
- less flexible
- dynamic linking
- compiles quicker
- smaller executable (how much smaller?)
- slower executable
- more flexible (can upgrade library on-the-fly)
- ls is dynamically linked against libc
$ file -L /lib64/libc.so.6
libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.4, not stripped
$ file -L libc.so.6
libc.so.6: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.4, not stripped
be careful, can hose your system
Macros: the #define
preprocessor directive
May be defined with or without arguments
- a macro without arguments is processed like a symbolic constant
(e.g., #define TRUE 1)
- a macro with arguments is processed like a function call without the
stack overhead
- #define identifier(arg[, arg] ...) token-string
- short routine which accepts arguments
- upper-case convention
- parenthesize if used with operators
#define SQUARE(X) ((X)*(X))
#define PRINT(A, B) printf(#A ": %d, " #B ": %d\n", A, B)
main() {
int x = SQUARE(3);
int y = SQUARE(x+1);
PRINT(x, y);
}
- output: x: 9, y: 100
- #A in a replacement string of a macro
- replace by an actual parameter
- enclose it in quotes
- expanded source code
main() {
int x = ((3)*(3));
int y = ((x+1)*(x+1));
printf("x" ": %d, " "y" ": %d\n", x, y);
}
macros are not quite as expressive as C++ templates;
consider how things can go terribly awry
#define MAX(A, B) ((A) > (B)) ? (A) : (B)
this generic works fine as long as the parameters are free of side-effects;
MAX(x++, y) expands to ((x++) > (y) ? (x++) : (y))
and therefore `whenever the value of x is greater than that of y,
x will be incremented twice' [CPLS] (p. 387)
3 versions of swap function (call-by-value, call-by-reference, macro)
new macros
Macros vs. functions
- speed
- macros faster; in-line replacement
- functions slower; have stack overhead
- size of executable program;
smaller if functions used; code appears once
- software engineering principles
(decomposition, functional overhead, and readability) vs. efficiency
- other
- functions can return value with return statement; macros cannot
- recursion impossible with macros
- macros are often more challenging to debug
Simple macro vs. constant
- #define PI 3.14
- #define TRUE 1
- #define FALSE 0
- just a macro, no memory allocated
- C preprocessor replaces
- float pi = 3.14;
- typedef int bool;
- which is more efficient in time or space?
Conditional compilation
#if, #ifdef, and #ifndef
- conditionally adds C and/or preprocessor directives to a program
- allows us to simulate multiple versions of a program from a single
source file
- helpful for controlling the inclusion/exclusion of echo prints for
debugging
- each of the conditional preprocessor directives evaluates a constant
integer expression
#include "local.h"
/* code ref. [C] (4-27) with minor modifications */
/* we would normally indent the body of conditional,
but not permitted here */
#if vax || u3b || u3b5 || u3b2
#define MAGIC 330
#else
#define MAGIC 500
#endif
#ifdef LIMIT
#undef LIMIT
#endif
#define LIMIT 1000
/* when return type omitted, int assumed */
f() {
/* allowed to indent here */
...
/* to use debugging statements, #define DEBUG
anywhere before #ifdef finds it;
or use gcc -DDEBUG pgm.c */
#ifdef DEBUG
printf ("x is %d\n", x);
printf ("y is %d\n", y);
#endif
/* allowed to indent here */
...
}
C preprocessor is not as intelligent as the C compiler when evaluating
Error handling
- importance cannot be stressed enough; key to reliability
- usually functions return -1 or NULL and set errno
to indicate an error
- perror (in stdio.h)
and strerror (in string.h);
be careful, strerror may change the value of errno;
therefore save and restore it
#include<stdio.h>
#include<string.h>
#include<errno.h>
main() {
int errnobakup;
int fildes;
if (close(fildes) == -1) {
errnobakup = errno;
perror ("Failed to close the file.");
fprintf (stderr, "Failed to close file: %s\n", strerror(errno));
errno = errnobakup;
}
}
- ferror
- explicitly set errno for all errors, and
do not rely on the fact
that a function which fails may set errno automatically for you
- common errors include exceeding available memory or file I/O
open/close, read/write errors
- see the GNU
webpage for libc
for a list error codes which are #defined in error.h (e.g.,
use ENOMEM for the former and EIO for the latter errors above)
- interruptions by signals;
restart.h, use r_ prefix (e.g., r_close)
Debugging
- lint
- gdb
- ddd
- truss
- leaks
- heap
- use conditional compilation: #ifdef and #endif,
#define DEBUG or gcc -DDEBUG
References
| [C] |
C Language for Experienced Programmers, Version 2.0.0, AT&T, 1988. |
| [CPL] |
B.W. Kernighan and D.M. Ritchie. The C Programming Language.
Prentice Hall, Upper Saddle River, NJ, Second edition, 1988. |
| [UPE] |
B.W. Kernighan and R. Pike. The UNIX Programming Environment.
Prentice Hall, Upper Saddle River, NJ, Second edition, 1984.
|
| [USP] |
K.A. Robbins and S. Robbins.
UNIX Systems Programming: Concurrency, Communication, and Threads.
Prentice Hall, Upper Saddle River, NJ, Second edition, 2003
|
|