Você está na página 1de 117

ADVANCED C

-¨˜”°º•Calypso•º°”˜¨
Memory Management
• A process is an "invocation" or "activation" of a program. A program is a
list of instructions for execution by a computer.
• To run the program it needs to be copied (or loaded) into the main
computer memory and the central processing unit is told to start reading
(and obeying) instructions taken from that area of memory. The activity of
executing the program's instructions is called running the process.
• A portion of memory needs to be allocated to a process. The actual
allocation of such an area of memory is a task for the loader.
• The memory area allocated to a program will usually be split into several
sub-areas for particular.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 2
Memory management
The code segment

• This is known as the text area in Unix parlance and simply contains the
executable code of the program(both the program binary and any shared
library it loads).
• If there are several processes running the same program there will still
only be one code area as it is identical for all processes.
• The current state of a process is determined by its various data areas.
• All memory pages of code segment are marked as read-only and are
shared with any other process using the same program file and/or shared
library files.
• The kernel process exchange (or switch) mechanism will activate a
particular process by indicating to the hardware where the next
instruction is to be read from what mapping is to be used to access data
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 3
Memory Management
The data segment

• This holds the data being processed by the program, it's size is initially
determined by the loader from information in the binary file which
specifies the amount of initialized data to be copied form the binary file
and the amount of un initialized space to be allocated to the process.
• On some systems the space may be initialized to zero but this is not
universal and such areas may contain "droppings" left over from previous
processes, a possible security problem. On older Unix systems the un
initialized space is known as bss from a PDP/11 assembler mnemonic.
• The Unix size command will give the memory requirement information
for a binary file.
• This segment can grow as the process runs and needs more virtual
memory.
• bash$ size testhand2 92763 + 7564 + 2320 = 102647
• The above example shows that loading the binary file testhand2 will
require 102647 bytes of memory with 92763 bytes for code, 7564 bytes
for initialized data and 2320¨˜”°º•Calypso•º°”˜¨
12/07/21
bytes for non-initialized data. 4
Memory Management
The stack segment

• Whenever a function is called a stack frame is created to


store data related to the current invocation of the function
such as the return address, saved state, local copies of
parameters and, of course, local data.
• The stack is normally built within the data area of the
process and the memory allocated to the stack can be
expanded as needed.
• The stack and the arena need to be kept separate from the
initialized and un initialized data areas, this is normally
achieved by using widely separated parts of a large virtual
address space.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 5
Memory Management
Heap memory

• The heap is a portion of memory allocated dynamically (as needed, at


runtime) for the use of the process.
• Whenever the malloc or calloc functions are used in C for instance,
they reserve space in heap memory.

Stack

• The stack portion of memory, on the other hand, is used by the process
to aid the invocation of functions.
• Every time a function is called, the process reserves a portion of stack
memory to store the values of parameters passed to the functions as
well as for results returned by the functions and the local variables used
within the functions.
• The stack is also where space for all declared data types and structures
is reserved at compile time.¨˜”°º•Calypso•º°”˜¨
12/07/21 6
Memory Management
The Stack And Local Variables
• The stack is used by the process to store the chain of functions which are
currently in the middle of execution.
• Each function call causes the process to add an execution frame to the
top of the stack. This execution frame would contain the contents of
CPU registers as they were before the function call was made, the return
address (i.e. where in the code this function was invoked, so we can
return there when the function returns), the parameters passed to the
function and all local variables of the function.
• Thus, when we have function 'main' calling function 'a' which calls
function 'b', we will have 3 frames on the stack.
• The 'allocation' of a stack frame does not require performing real
memory allocation - as the space for the stack was reserved during
process startup. Instead, this is done by merely marking a part of the
stack as containing the frame, and copying data to that frame.
• Note that local variables are "allocated" by simply advancing the stack
pointer beyond their location on the stack, so allocating local variables
takes a fixed amount of time, no matter their size.
•12/07/21
When a function returns - its¨˜”°º•Calypso•º°”˜¨
stack frame is freed by simply modifying 7
the stack pointer to point below its frame.
Memory Management
The Stack And Local Variables(cont..)
• The local variables are not initialized - they just contain the values that
were accidentally placed in the memory locations these variables
occupy.
• Consider a situation in which a function was called, its variables used
and given some values.
• Later the function returned, and its frame was released.
• Then, another function was called.
• The stack frame of this new function is located in the same place in
memory as the frame of the former function, so the new function's local
variables will get the values that were left there by the local variables of
the former function.
• This can explain why the values of un initialized local variables are often
neither 0, nor look like total garbage.
• Since all local variables are stored in consecutive memory on the stack,
if we take a pointer to such a variable, and later on write right above or
right below this pointer, we'll be modifying the contents of another local
variable, or even that of the function's return address.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 8
Memory Management
The Stack And Local Variables(cont..)
• For example, consider the following code:
int foo()
{
int numbers[2];
int j; j = 2;
printf("j - %d\n", j);
numbers[2] = 3;
printf("j - %d\n", j);
}
• During execution of this function, we first assign 2 to 'j', and thus the first
print command will show "j - 2". Then, we try to assign a value to
'numbers[2]'. However, the 'numbers' array only has 2 cells - 0 and 1.
Writing into subscript '2' of this array will cause us to write just beyond
the array (which is fully located on the stack).
• The variable 'j' just happens to be stored in that location in memory, and
thus, the value '3' will be actually assigned to 'j'.
• Our second print command will
12/07/21 thus show "j - 3".
¨˜”°º•Calypso•º°”˜¨ 9
Memory Management
The Stack And Local Variables(cont..)

• Note that this assumes that the variables are stored in memory in the
same order as they were declared inside the function's code. With
some compilers, this might not be the case, and the out-of-range
assignment might overwrite a different variable, or a part of the stack
that does not hold variables, leading to other unexpected results.
• Note: local variables (as well as function parameters) might be stored
in registers, rather than on the stack. This could be either because we
used the 'register' keyword when declaring these variables, or because
the compiler's optimization chose to place a variable in a register. Of
course, such variables cannot be over-written by stack overflows.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 10
Memory Management
Dynamic Memory Allocation
• Memory can be allocated dynamically by the calls
– malloc()
– calloc()
– realloc()
• The prototype for malloc is:
void *malloc(size_t size);
• malloc takes in a size_t and returns a void pointer.
• Why does it return a void pointer? Because it doesn't matter to malloc to
what type this memory will be used for.
• Let's see an example of how malloc is used:
int *ip;
ip = malloc(5 * sizeof(int)); /* .. OR .. */
ip = malloc(5 * sizeof(ip));
• Pretty simplistic. sizeof(int) returns the sizeof an integer on the machine,
multiply by 5 and malloc that many bytes.
• The second malloc works because
12/07/21 it sends what ip is pointing to, which
¨˜”°º•Calypso•º°”˜¨ 11
is an int.
Memory Management
Dynamic Memory Allocation(cont..)
• Wait... we're forgetting something. AH! We didn't check
for return values.
• Here's some modified code:
#define INITIAL_ARRAY_SIZE 5
/* ... code ... */
int *ip;
if ((ip = malloc(INITIAL_ARRAY_SIZE * sizeof(int))) == NULL)
{
(void)fprintf(stderr, "ERROR: Malloc failed");
(void)exit(EXIT_FAILURE); /* or return EXIT_FAILURE; */
}
• Now our program properly prints an error message and
exits gracefully if malloc fails.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 12
Memory Management
Dynamic Memory Allocation(cont..)
• calloc works like malloc, but initializes the memory to zero
if possible.
• The prototype is:
void *calloc(size_t nmemb, size_t size);

• bzero fills the first n bytes of the pointer to zero.


• Prototype:
void bzero(void *s, size_t n);
• If you need to set the value to some other value (or just as a
general alternative to bzero), you can use memset:
void *memset(void *s, int c, size_t n);
• where you can specify c as the value to fill for n bytes of
pointer s.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 13
Memory Management
Dynamic Memory Allocation(cont..)
• What if we run out of allocated memory during the run-time of our
program and need to give our collection of items more memory?
• Use realloc, it's prototype:
void *realloc(void *ptr, size_t size);
• realloc takes in the pointer to the original area of memory to enlarge
and how much the total size should be.
• So let's give it a try:
• ip = realloc(ip, sizeof(ip) + sizeof(int)*5);
• Ah... Now we have some more space, by simply giving it the sizeof the
complete array and then adding 5 spaces for ints.
• "I'm a genius!" you say... and then your program segfaults, core dumps
and you get laughed at.
• STOP! This is NOT how you use realloc.
• Again. The above example is wrong. Why?
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 14
Memory Management
Dynamic Memory Allocation(cont..)
• First, sizeof(ip) does not give the size of the allocated space originally
allocated by malloc (or a previous realloc).
• Using sizeof() on a pointer only returns the sizeof the pointer, which is
probably not what you intended.
• Also, what happens if the realloc on ip fails? ip gets set to NULL, and
the previously allocated memory to ip now has no pointer to it.
• Now we have allocated memory just floating in the heap without a
pointer. This is called a memory leak. This can happen from sloppy
realloc's and not using free on malloc'd space. So what is the correct
way? Take this code for example:
int *tmp;
if ((tmp = realloc(ip, sizeof(int) * (INITIAL_ARRAY_SIZE + 5))) ==
NULL)
{
/* Possible free on ip? Depends on what you want */
fprintf(stderr, "ERROR: realloc failed");
}
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 15
ip = tmp;
Memory Management
Dynamic Memory Allocation(cont..)
• Now we are creating a temporary pointer to try a realloc. If it fails, then
it isn't a big problem as we keep our ip pointer on the original memory
space.
• Also, note that we specified the real size of our original array and now
are adding 5 more ints (so 4bytes*(5+5) = 40bytes, on a typical 32-bit
machine).
• Now that we can malloc, calloc, and realloc we need to be able to free
the memory space if we have no use for it anymore. Like we
mentioned above, any memory space that loses its pointer or isn't free'd
is a memory leak.
• So what's the prototype for free? Here it is:
void free(void *ptr);
• free simply takes in a pointer to free. Not challenging at all. Note that
free can take in NULL, as specified by ANSI.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 16
Memory Management
Assigning memory to segments

• Compiler and assembler generate an object file (containing code and


data segments) from each source file
• Linker combines all the object files for a program into a single
executable object file, which is complete and self-sufficient
• Loader (part of OS) loads an executable object file into memory at
location(s) determined by the operating system
• Program (as it runs) uses new and malloc to dynamically allocate
memory, gets space on stack during function calls

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 17
Memory Management
Linking
Functions of a linker:
• Combine all files and libraries of a program
• Regroup all the segments from each file together (one big data segment,
etc.)
• Adjust addresses to match regrouping
• Result is an executable program

Contents of object files:


• File header — size and starting address(in memory) of each segment
• Segments for code and initialized data
• Symbol table (symbols, addresses)
• External symbols (symbols, location)
• Relocation information (symbols, location)
• Debugging information
• For UNIX details, type “man
12/07/21 a.out”
¨˜”°º•Calypso•º°”˜¨ 18
Memory Management
Why is Linking Difficult?
• When assembler assembles a file, it may find external references —
symbols it doesn’t know about (e.g., printf, scanf)
• Compiler just puts in an address of 0 when producing the object code
• Compiler records external symbols and their location (in object file) in
a crossreference list, and stores that list in the object file
• Linker must resolve those external references as it links the files
together
• Compiler doesn’t know where program will go in memory (if
multiprogramming, always 0 for uniprogramming)
• Compiler just assumes program starts at 0
• Compiler records relocation information (location of addresses to be
adjusted later), and stores it in the object file

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 19
Memory Management
Loading
• The loader loads the completed program into memory where it can be
executed
• Loads code and initialized data segments into memory at specified
location
• Leaves space for uninitialized data (bss)
• Returns value of start address to operating system
• Alternatives in loading
• Absolute loader — loads executable file at fixed location
• Relocatable loader — loads the program at an arbitrary memory
location specified by OS (needed for multiprogramming, not for
uniprogramming)
• Assembler and linker assume program will start at location 0
• When program is loaded, loader modifies all addresses by adding the
real start location to those addresses
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 20
Memory Management
• Running the Program — Static Memory Allocation
• Compiling, linking, and loading is sufficient for static memory
– Code, constants, static variables
• In other cases, static allocation is not sufficient:
– Need dynamic storage — programmer may not know how much memory
will be needed when program runs
• Use malloc or new to get what’s necessary when it’s necessary
• For complex data structures (e.g., trees),
• allocate space for nodes on demand
– OS doesn’t know in advance which procedures will be called (would be
– wasteful to allocate space for every variable in every procedure in
advance)
– OS must be able to handle recursive procedures

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 21
Memory management
Running the Program — Dynamic Memory Allocation
• Dynamic memory requires two fundamental operations:
– Allocate dynamic storage
– Free memory when it’s no longer needed
– Methods vary for stack and heap
• Two basic methods of allocation:
– Stack (hierarchical)
• Good when allocation and freeing are somewhat predictable
• Typically used:
– to pass parameters to procedures
– for allocating space for local variables inside a procedure
– for tree traversal, expression evaluation,parsing, etc.
• Use stack operations: push and pop
• Keeps all free space together in a structured organization
• Simple and efficient, but restricted

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 22
Memory Management
Running the Program — Dynamic Memory Allocation
(cont.)
• Two basic methods of allocation:
– Heap
• Used when allocation and freeing are not predictable
• Typically used:
– for arbitrary list structures, complex data organizations, etc.
• Use new or malloc to allocate space, use delete or free to release space
• System memory consists of allocated areas and free areas (holes)
• Problem: eventually end up with many small holes, each too small to be useful
– This is called fragmentation, and it leads to wasted memory
– Fragmentation wasn’t a problem with stack allocation, since we always add/delete
from top of stack
– Solution goal: reuse the space in the holes in such a way as to keep the number of
holes small, and their size large
• Compared to stack: more general, less efficient, more difficult to implement

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 23
Strings
• Stings in C are stored as null character, '\0', terminated character arrays.
• This means that the length of a string is the number of characters it
contains plus one to store the null character.
• The subscripts used for the array start with zero (0).
• Common string operations include finding lengths, copying, searching,
replacing and counting the occurrences of specific characters and words.
• #include <string.h> needs to be included when string functions are used
• The following line declares a char array called str.
char str[15];
• C provides fifteen consecutive bytes of memory.
• Only the first fourteen bytes are usable for character storage, because
one must be used for the string-terminating null.
• The following is a representation of what would be in RAM, if the string
"Hello, world!" is stored in this array.
• Characters: H e l l o , w o r l d !
• Hex values: 48 65 6C 6C 6F 2C 20 77 6F 71 6C 64 21 00
• Subscripts: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 24
Strings
Common String Functions
strcpy
strcpy copies a string, including the null character terminator from the
source string to the destination. This function returns a pointer to the
destination string, or a NULL pointer on error. Its prototype is:
char *strcpy(char *dst, const char *src);

strncpy
strncpy is similar to strcpy, but it allows the number of characters to
be copied to be specified. If the source is shorter than the destination,
than the destination is padded with null characters up to the length
specified. This function returns a pointer to the destination string, or a
NULL pointer on error. Its prototype is:
char *strncpy(char *dst, const char *src, size_t len);

strcat
This function appends a source string to the end of a destination
string. This function returns a pointer to the destination string, or a
NULL pointer on error. Its prototype is:
char *strcat(char *dst, const char *src);
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 25
Strings
Common String Functions(Cont..)
strncat
This function appends at most N characters from the source string to the end
of the destination string. This function returns a pointer to the destination
string, or a NULL pointer on error. Its prototype is:
char *strncat(char *dst, const char *src, size_t N);

strcmp
This function compares two strings. If the first string is greater than the
second, it returns a number greater than zero. If the second string is greater, it
returns a number less than zero. If the strings are equal, it returns 0. Its
prototype is:
int strcmp(const char *first, const char *second);

strncmp
This function compares the first N characters of each string. If the first string
is greater than the second, it returns a number greater than zero. If the second
string is greater, it returns a number less than zero. If the strings are equal, it
returns 0. Its prototype is:
int strncmp(const char *first,
12/07/21 const char *second, size_t N);
¨˜”°º•Calypso•º°”˜¨ 26
Strings
Common String Functions(Cont..)
strlen
This function returns the length of a string, not counting the null character
at the end. That is, it returns the character count of the string, without the
terminator. Its prototype is:
size_t strlen(const char *str);

strchr
This function finds the first occurrence of character C in string STR,
returning a pointer to the located character. It returns NULL pointer
when there is no match
char * strchr (char *STR, char C)

strstr
This function is like `strchr' but searches for string SEARCH in string
STR, returning a pointer into the STR that is the first character of the
match found in STR. It returns NULL pointer if no match was found. If
SEARCH is an empty string, it returns STR.
char * strstr (const char *STR,
12/07/21 const char *SEARCH)
¨˜”°º•Calypso•º°”˜¨ 27
Bitwise Operators
• Bitwise operators apply the same operation to matching bits in value on
each side of operator (the one's complement is unary and works only
on the value to it's right)
• Result for each bit position determined only by the bit(s) in that
position
• Results for each operator summarized in the following table

a 0 0 1 1
b 0 1 0 1
and a&b 0 0 0 1
or a|b 0 1 1 1
exclusive or a^b 0 1 1 0
one's complement ~a 1 1 0 0

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 28
Bitwise Operators
Shift Operators
• Operators >> and << can be used to shift the bits of an operand to the right
or left a desired number of positions
• The number of positions to be shifted can be specified as a constant, in a
variable or as an expression
• Can be used on any of the integral data types - char, short, <VARINT<
VAR>or long
• Bits shifted out are lost
• For left shifts, bit positions vacated by shifting always filled with zeros
• For right shifts, bit positions vacated by shifting filled
– with zeros for unsigned data type
– with copy of the highest (sign) bit for signed data type
• Applications
– quick multiplication by a power of 2
– quick division by power of 2 (unsigned types only)
– bit-mapped record processing
– packing / unpacking data less that byte size
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 29
Bitwise Operators
Shift operators(contd..)
• Can use << to multiply integral value by a power of two
• 1 bit multiplies by 2, 2 bits multiplies by 4, 3 bits multiplies by 8, n bits multiplies by
2n
• On some implementations, shifting may be much faster than multiplying (but good
ol' multiplication makes for much clearer code)
• Using left shift to multiply:
long x = 75, y;
int i;
...
x <<= 2; /* x = 75 * 4 = 300 */
y = x << 1; /* y = ( x * 2 = 600 ) x is not changed */
x = 6;
for ( i = 0; i < 5; i++ )
{
printf( "%d\n", x << i );
}
Prints
6 6 << 0 is 6*1
12 6 << 1 is 6*2
24 6 << 2 is 6*4
48 6 << 3 is 6*8
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 30
96 6 << 4 is 6*16
Bitwise Operators
Shift operators(contd..)
• Can use >> to divide unsigned integral value by a power of two.
• 1 bit divides by 2, 2 bits divides by 4, 3 bits divides by 8, n bits
divides by 2n
• On some implementations, shifting may be much faster than dividing
(but division makes for much clearer code)
• Shifting signed values may fail because for negative values the result
never gets past -1:
• -5 >> 3is -1 and not 0 like -5/8
• Using right shift to divide:
unsigned long x = 75, y;
...
x >>= 2; /* x = 75 / 4 = 18 */
y = x >> 1; /* y = ( x / 2 = 9 ) x is not changed */

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 31
Structures
• Structure in C is a collection of items of different types. You can think
of a structure as a "record" is in Pascal or a class in Java without
methods.
• So how is a structure declared and initialized? Let's look at an example:
struct student
{
char *first;
char *last;
char SSN[9];
float gpa;
char **classes;
};
struct student student_a, student_b;
Structures :: Declaration and Syntax

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 32
Structures
• Another way to declare the same thing is:
struct
{
char *first;
char *last;
char SSN[10];
float gpa;
char **classes;
} student_a, student_b;
• As you can see, the tag immediately after struct is optional. But in the
second case, if you wanted to declare another struct later, you couldn't.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 33
Structures
• The "better" method of initializing structs is:
struct student_t
{
char *first;
char *last;
char SSN[10];
float gpa;
char **classes;
} student, *pstudent;
• Now we have created a student_t student and a
student_t pointer. The pointer allows us greater
flexibility (e.g. Create lists of students).
• You could initialize a struct just like an array
initialization. But be careful, you can't initialize this
struct at declaration time because of the pointers.
• To access fields inside struct C has a special operator for
this called "member of" operator denoted by . (period).
For example, to assign¨˜”°º•Calypso•º°”˜¨
12/07/21 the SSN of student_a: 34
strcpy(student_a.SSN, "111223333\0");
Structures
Nested structures
• Structures can contain other structures as members; in other words,
structures can nest. Consider the following two structure types:
struct first_structure_type
{
int integer_member;
float float_member;
};
struct second_structure_type
{
double double_member;
struct first_structure_type struct_member;
};
• The first structure type is incorporated as a member of the second
structure type.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 35
Structures
Nested structures(Contd..)
• You can initialize a variable of the second type as follows:
struct second_structure_type demo;
• demo.double_member = 12345.6789;
demo.struct_member.integer_member = 5;
demo.struct_member.float_member = 1023.17;
• The member operator . is used to access members of structures that are
themselves members of a larger structure.
• No parentheses are needed to force a special order of evaluation
• A member operator expression is simply evaluated from left to right.
• In principle, structures can be nested indefinitely.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 36
Typedef
• There is an easier way to define structs or you could "alias" types you
create.
• For example:
typedef struct
{
char *first;
char *last;
char SSN[9];
float gpa;
char **classes;
} student; student student_a;
• Now we get rid of those silly struct tags. You can use typedef for non-
structs: typedef long int *pint32; pint32 x, y, z; x, y and z are all
pointers to long ints. typedef is your friend. Use it.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 37
Union
• Unions are declared in the same fashion as structs, but have a
fundamental difference. Only one item within the union can be used at
any time, because the memory allocated for each item inside the union
is in a shared memory location. Why you ask? An example first:
struct conditions
{
float temp;
union feels_like
{
float wind_chill;
float heat_index;
}
} today;
• As you know, wind_chill is only calculated when it is "cold" and
heat_index when it is "hot". There is no need for both. So when you
specify the temp in today, feels_like only has one value, either a float
for wind_chill or a float for heat_index.
• Types inside of unions are unrestricted, you can even use structs within
12/07/21
unions. ¨˜”°º•Calypso•º°”˜¨ 38
Enum
• What if you wanted a series of constants without creating a new type?
Enter enumerated types. Say you wanted an "array" of months in a
year:
enum e_months {JAN=1, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC};
typedef enum e_months month;
month currentmonth;
currentmonth = JUN; /* same as currentmonth = 6; */
printf("%d\n", currentmonth);
• We are enumerating the months in a year into a type called month.
You aren't creating a type, because enumerated types are simply
integers. Thus the printf statement uses %d, not %s.
• If you notice the first month, JAN=1 tells C to make the enumeration
start at 1 instead of 0.
• Note: This would be almost the same as using:
• #define JAN 1
• #define FEB 2
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 39
• #define MAR 3 /* ... etc ... */
Static Variables
• A static variable is local to particular function. However, it is only
initialised once (on the first call to function).
• Also the value of the variable on leaving the function remains intact.
On the next call to the function the the static variable has the same
value as on leaving.
• To define a static variable simply prefix the variable declaration with
the static keyword. For example:
•   void stat(); /* prototype fn */   main()   { int i;   for (i=0;i<5;++i)
stat();   }   stat()   { int auto_var = 0; static int static_var = 0;  
printf( ``auto = %d, static = %d n'', auto_var, static_var); +
+auto_var; ++static_var;   }
• Output is:
•   auto_var = 0, static_var= 0 auto_var = 0, static_var = 1 auto_var =
0, static_var = 2 auto_var = 0, static_var = 3 auto_var = 0, static_var
=4

• Clearly the auto_var variable is created each time. The static_var is


created once and remembers its value.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 40
Bitfields
• Bit Fields allow the packing of data in a structure. This is especially
useful when memory or data storage is at a premium. Typical examples:
• Packing several objects into a machine word.
• e.g. 1 bit flags can be compacted -- Symbol tables in compilers.
• Reading external file formats -- non-standard file formats could be read
in. E.g. 9 bit integers.
• C lets us do this in a structure definition by putting :bit length after the
variable. i.e.
struct packed_struct
{
unsigned int f1:1;
unsigned int f2:1;
unsigned int f3:1;
unsigned int f4:1;
unsigned int type:4;
unsigned int funny_int:9;
} pack;
Here the packed_struct contains
12/07/21 6 members: Four 1 bit flags f1..f3, a 4 bit
¨˜”°º•Calypso•º°”˜¨ 41
type and a 9 bit funny_int.
Bitfields
• C automatically packs the above bit fields as compactly as
possible, provided that the maximum length of the field is
less than or equal to the integer word length of the
computer. If this is not the case then some compilers may
allow memory overlap for the fields whilst other would
store the next field in the next word

• NOTE:
• Only n lower bits will be assigned to an n bit number. So
type cannot take values larger than 15 (4 bits long).
• Bit fields are always converted to integer type for
computation.
• You are allowed to mix ``normal'' types with bit fields.
• The unsigned definition is important - ensures that no bits
are used as a flag.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 42
Bitfields
Bit Fields: Practical Example
• Frequently device controllers (e.g. disk drives) and the operating system need to
communicate at a low level. Device controllers contain several registers which may
be packed together in one integer  
• We could define this register easily with bit fields:
struct DISK_REGISTER
{
unsigned ready:1;
unsigned error_occured:1;
unsigned disk_spinning:1;
unsigned write_protect:1;
unsigned head_loaded:1;
unsigned error_code:8;
unsigned track:9; u
nsigned sector:5;
unsigned command:5; };
• To access values stored at a particular memory address,
DISK_REGISTER_MEMORY we can assign a pointer of the above structure to
access the memory via:
• struct DISK_REGISTER *disk_reg = (struct DISK_REGISTER *)
DISK_REGISTER_MEMORY;

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 43
Bitfields
• The disk driver code to access this is now relatively straightforward:
/* Define sector and track to start read */
disk_reg->sector = new_sector;
disk_reg->track = new_track;
disk_reg->command = READ; /* wait until operation done, ready will
be true */
while ( ! disk_reg->ready ) ; /* check for errors */
if (disk_reg->error_occured)
{ /* interrogate disk_reg->error_code for error type */
switch (disk_reg->error_code) ......
}
A note of caution: Portability
• Bit fields are a convenient way to express many difficult operations.
However, bit fields do suffer from a lack of portability between
platforms
• integers may be signed ¨˜”°º•Calypso•º°”˜¨
12/07/21 or unsigned 44
Bitfields
• Many compilers limit the maximum number of bits in the bit field
to the size of an integer which may be either 16-bit or 32-bit
varieties.
• Some bit field members are stored left to right others are stored
right to left in memory.
• If bit fields too large, next bit field may be stored consecutively in
memory (overlapping the boundary between memory locations)
or in the next word of memory.
• If portability of code is a premium you can use bit shifting and
masking to achieve the same results but not as easy to express or read.
For example:
• unsigned int *disk_reg = (unsigned int *)
DISK_REGISTER_MEMORY; /* see if disk error occured */
disk_error_occured = (disk_reg & 0x40000000) >> 31;

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 45
Typecasting
• C is one of the few languages to allow coercion, that is forcing one
variable of one type to be another type. C allows this using the cast
operator (). So:
int integernumber;
float floatnumber=9.87;  
integernumber=(int)floatnumber;

• assigns 9 (the fractional part is thrown away) to integernumber.

• And:
int integernumber=10;
float floatnumber;  
floatnumber=(float)integernumber;

• assigns 10.0 to floatnumber.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 46
Typecasting
• Coercion can be used with any of the simple data types including char, so:
int integernumber;
char letter='A';  
integernumber=(int)letter;
• assigns 65 (the ASCII code for `A') to integernumber.
• Some typecasting is done automatically -- this is mainly with integer
compatibility.

• A good rule to follow is: If in doubt cast.

• Another use is the make sure division behaves as requested:


• If we have two integers internumber and anotherint and we want the
answer to be a float then :

• e.g.  floatnumber =  (float) internumber / (float) anotherint;


ensures floating point division.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 47
Preprocessor and Macros
• Preprocessor includes, substitutes and selects text to form finished
source code prior to actual compile
• #include used to copy in statements from other files, usually header
files with .h extension
• #define used to define macros
– simple substitution
– function-like substitution with arguments
– #undef used to remove previous definition
• Conditional commands used to selectively include or exclude
statements
– #if, #elif, #else, #endif
– #ifdef and #ifndef test if a name is defined
– defined() operator test if a name is defined
• Predefined macros
– __FILE__Source file name
– __LINE__Current source line number
– __DATE__Date compiled

12/07/21 __TIME__Time compiled¨˜”°º•Calypso•º°”˜¨ 48
– __TIMESTAMP__Compile date/time
Preprocessor and Macros
#include statement
• #include copies code from external files
• Files copied generally have .h file extension and considered "header
file"
• Usually included at beginning of source module
• May be nested - included file may contain #include
• Use of brackets or quotes determines where compiler searches for the
included file
#include "stuff2.h"
• If file name is in quotes, looks first in the same location as the module
with the #include statement
#include <stuff2.h>
• If file name is in brackets, searches according to an implementation-
defined rule.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 49
Preprocessor and Macros
#define statement
We already saw how #define can be used to define a simple
substitution text for a symbol.
• Format
#define identifier replacment-text
#define PI 3.14159
#define NUM_ROW (sizeof(ax)/sizeof(*ax))
#define LOG_FILE "hacmp.log"
• Definition can also be function-like, with arguments
#define identifier(arg1,arg2) repl-text
• When macro is invoked, argument tokens appearing in the
macro replacement text are replaced with the text for that
argument in the macro invocation.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 50
Preprocessor and Macros
• #if directive
#if is followed by a intger constant expression.
• If the expression is not zero, the statement(s) following the #if are
compiled, otherwise they are ignored.
• #if statements are bounded by a matching #endif, #else or #elif
• Macros, if any, are expanded, and any undefined tokens are replaced
with 0 before the constant expression is evaluated
• Relational operators and integer operators may be used
• Expression examples
• #if 1
• #if 0
• #if ABE == 3
• #if ZOO < 12
• #if ZIP == 'g'
• #if (ABE + 2 - 3 * ZIP) > (ZIP - 2)
• In most uses, expression is simple relational, often equality test
• #if SPARKY == '7'
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 51
Preprocessor and Macros
#else directive
#else marks the beginning of statement(s) to be compiled if the preceding #if
or #elif expression is zero (false)
• Statements following #else are bounded by matching #endif
• Examples #if OS = 'A' system( "clear" ); #else system( "cls" ); #endif
#elif directive
#elif adds an else-if branch to a previous #if
• A series of #elif's provides a case-select type of structure
• Statement(s) following the #elif are compiled if the expression is not zero,
ignored otherwise
• Expression is evaluated just like for #if
• Examples
#if TST == 1 z = fn1( y );
#elif TST == 2 z = fn2( y, x );
#elif TST == 3 z = fn3( y, z, w );
#endif ...
#if ZIP == 'g' rc = gzip( fn );
#elif ZIP == 'q' rc = qzip( fn );
#else rc = zip( fn );
12/07/21
#endif ¨˜”°º•Calypso•º°”˜¨ 52
Preprocessor and Macros
• Conditonal compilation
The following preprocessor directives are used for conditional
compilation. Conditional compilation allows statements to be included
or omitted based on conditions at compile time. #if #else #elif #endif
#ifdef #ifndef
• In the following example, the printf statements are compiled when the
symbol DEBUG is defined, but not compiled otherwise
• /* remove to suppress debug printf's*/
#define DEBUG
...
x = ....
#ifdef DEBUG
printf( "x=%d\n" );
#endif
...
y = ....;
#ifdef DEBUG
printf( "y=%d\n" );
#endif ...
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 53
Pointers
• Pointers provide an indirect method of accessing variables.
• You may be wondering, what is the point of this (no pun intended)?
• Why don't I just make all variables without the use of pointers?
• It's because sometimes you can't.
• What if you needed an array of ints, but didn't know the size of the
array before hand?
• What if you needed a string, but it grew dynamically as the program
ran?
• They are all solved through the use of pointers. Pointers are also
essential in creating larger custom data structures, such as linked lists.
• A pointer when declared is just a reference. DECLARING A
POINTER DOES NOT CREATE ANY SPACE FOR THE
POINTER TO POINT TO. We will tackle this dynamic memory
allocation issue later.
• A pointer is a reference to an area of memory in the heap. The heap is
a dynamically allocated area of memory when the program runs.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 54
Pointers
• Pointers are declared by using the “*” infront of the variable identifier.
• For example:
int *ip;
float *fp = NULL;
char *a;
• This declares a pointer, ip, to an integer.
• Let's say we want ip to point to an integer.
• The second line declares a pointer to a float, but initializes the pointer to
point to the NULL pointer. The NULL pointer points to a place in
memory that cannot be accessed. NULL is useful when checking for
error conditions and many functions return NULL if they fail.
• Third line declares pointer to a char.
int x = 5;
int *ip;
ip = &x;
• The & operator is to specify the address-of x. Thus, the pointer, ip is
pointing to x by assigning the address of x.
• The * dereferences the pointer to the value.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 55
• So, printf("%d %d\n", x, *ip); would print 5 5 to the screen.
Pointers
• What is the output of this program?
main()
{
int i = 54;
float a = 3.14;
char *ii, *aa;
ii = & i;
aa = & a;
printf(“\nAddress contained in ii = %u”, ii);
printf(“\nAddress contained in ii = %u”, ii);
printf(“\nValue at the address contained in ii = %d”, *ii);
printf(“\nValue at the address contained in ii = %d”, *aa);

} i
a

Binary equivalent of 54 Binary equivalent of 3.14


12/07/21 ¨˜”°º•Calypso•º°”˜¨ 56
2008 2009 7006 7007 7008 7009
Pointers
• Output
• ii and aa are declared as char pointers. Still the statements ii = &i and aa
= &a work.
• The addresses 2008 and 7006 get stored in ii and cc which are printed
through the first two printf()s.
• However the program falters at the next two printf()s.
• This is so since ii is a character pointer *ii gives value at address 2008
and not the one present in 2008 and 2009.
• Similarly *aa gives the value at 7006 and not the one contained in 7006,
7007, 7008 and 7009.
• Moral is if you wish to access an integer value stored in a variable using
its address it’s necessary that the address be stored in an integer pointer.
Likewise if you wish to access a float value stored in a variable using
its address it’s necessary to store the address in a float pointer.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 57
Pointers
Functions Returning Pointers
• The way functions return an int, a float, a double or any other data type, it
can even return a pointer.
• However, to make a function return a pointer it has to be explicitly
mentioned in the calling function as well as in the function declaration.
• Example:
main()
{
int *p;
int *fun();
p=fun();
printf(“\n%u”,p);
}
int *fun()
{
int i=20;
return (&i);
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 58
}
Pointers
Pointer Arithmetic
• C is one of the few languages that allows pointer arithmetic.
• In other words, you actually move the pointer reference by an
arithmetic operation.
• For example:
• int x = 5,
• *ip = &x;
• ip++;
• On a typical 32-bit machine,
• *ip would be pointing to 5 after initialization.
• But ip++; increments the pointer 32-bits or 4-bytes.
• So whatever was in the next 4-bytes, *ip would be pointing at it.
• Pointer arithmetic is very useful when dealing with arrays, because
arrays and pointers share a special relationship in C.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 59
Pointers
Pointer Arithmetic(Contd…)
• What is the output?
main()
{
float *fun(float *);
float p=23.5, *q;
q = &p;
printf(“\nq before call = %u”,q);
q = fun(&p);
printf(“\nq after call = %u”,q);
}
float *fun(float *r)
{
r = r+1;
return(r);
}
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 60
Pointers
Pointer Arithmetic(Contd…)
• Output
• q before call = 5498
• q after call = 5502
• In main(), q has been declared as a float pointer. It means q is a variable
capable of holding the address of a float. Through q= &p the address of
p, a float value is stored in q and then printed out through the printf().
This is the value before fun() is called. When fun() is called the address
of p is sent to it and is collected in r. At this juncture r contains 5498.
When r is incremented it would become 5502. r is a float pointer and on
incrementing by 1 it would point to the next float which would be
present 4 bytes hence, since every float is 4 bytes long. The return
statement then return this address 5502 back to main()

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 61
Pointers
Pointer Arithmetic(Contd…)
• Pointers can be incremented and can be decremented as well, to point to
earlier locations. Thus the following operations can be performed
(a) Addition of a number to a pointer. For example,
int i=4,*j,*k;
j=&i;
j=j+1;
j=j+9;
k=j+3;
(b) Subtraction of a number from a pointer. For example,
int i=4,*j,*k;
j=&i;
j=j-2;
j=j-5;
k=j-6;
A word of caution… Do not attempt the following operations on pointers..
They never work out
(a) Addition of two pointers
(b) Multiplying a pointer with a number
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 62
(c) Dividing a pointer with a number
Pointers
• What is the output?
main()
{
int a[]={10,20,30,40,50};
int j;
for (j=0;j<5;j++)
{
printf(“\n%d”,*a);
a++;
}
}
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 63
Pointers
• Output
• Error message:Lvalue required in function main
• Whenever we mention the name of the array, we get its base address.
Therefore for the first time through the loop, the printf() should print
the value at its base address. There is no problem up to this. The
problem lies in the next statement, a++. Since C does not perform
bounds checking on an array, the only thing it remembers about an
array once declared is its base address. And a++ attempts to change
this base address, which C won’t allow because if it does so, it would
be unable to remember the beginning of the array. Anything which can
change in compiler’s language is called lvalue. Since value of a cannot
be changed through ++, it flashes the error saying ‘L value required’
so that ++operator can change it.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 64
Pointers
• What is the output?
main()
{
float a[]={13.24,1.5,1.5,5.4,3.5};
Float *j,*k;
j = a;
k = a + 4;
j = j * 2;
k = k / 2;
printf(“\n%f %f”,*j,*k);
}

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 65
Pointers
• Output
• Error message: Illegal use of pointer in
function main
• j and k have been declared as pointer variables, which
would contain the addresses of floats. In other words, j and
k are the pointers. To begin with, the base address of the
array a[] is stored in j. The next statement is perfectly
acceptable; the address of the 4th float from the base
address is stored in k. The next two statements are
erroneous. This is because the only operations that can be
performed on pointers are addition and subtraction.
Multiplication and division of a pointer is not allowed.
Hence the error message.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 66
Pointers
Pointers to structs
• Sometimes it is useful to assign pointers to structures.
• Declaring pointers to structures is basically the same as declaring a
normal pointer:
• struct student *student_a;
• But how do we dereference the pointer to the struct and its fields? You
can do it in one of two ways, the first way is:
• printf("%s\n", (*student_a).SSN);
• This would get the SSN in student_a. Messy and the readability is
horrible! Is there a better way? Of course, programmers are lazy! :)
• To dereference, you can use the infix operator: ->. The above example
using the new operator:
• printf("%s\n", student_a->SSN);
• If we malloc'd space for the structure for *student_a could we start
assigning things to pointer fields inside the structure? No. You must
malloc space for each individual pointer within the structure that is
being pointed to.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 67
Pointers
Common error while using pointers:
• Using an uninitialized pointer.
– Remember, declaring a pointer variable simply allocates a cell that
can hold a pointer - it does not place a value in the cell. So, for
example, a code fragment like:
{
int * iptr;
*iptr = 2;
...
}
will attempt to place the value, 2, in the cell pointed to by iptr;
however, iptr has not been initialized, so some garbage value will
be used as the address of there to place the value. On some
systems this may result in an attempt to access an illegal address,
12/07/21
and a memory violation. Avoid this error by remembering to 68
¨˜”°º•Calypso•º°”˜¨
initialize all pointer variables before they are used.
Pointers
Common error while using pointers(Contd…)
• Instead of using a pointer to an object, a pointer to a pointer
is used.
– Consider a function, read_int(). It reads an integer and stores it
where its argument points. The correct version is:
void read_int(int * pn)
{
scanf("%d", pn);
}
pn is a pointer to the object where the integer is to be stored. When
passing the argument to scanf(), we pass the pointer, pn, NOT &pn.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 69
Pointers
Common error while using pointers(Contd…)
• Confusion between the address of operator and the dereference operator.
...
calling_func(...)
{
int x;
called_func(*x); /* should be &x */ ... }
...
called_func(int &px) /* should be * px */
{
...
}
• A useful mnemonic aid is that the ``address of'' operator is the ``and''
symbol, & --- both start with letter, a.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 70
Function Pointers
• Function Pointers are pointers, i.e. variables, which point to the
address of a function
Ex: int (*pt2Function) (float, char, char);
//assign an address to the function pointer
int DoIt (float a, char b, char c)
{
printf("DoIt\n”);
return a+b+c;
}
int DoMore(float a, char b, char c)
{
printf("DoMore\n";
return a-b+c;
}
pt2Function = DoMore; // assignment
pt2Function = &DoIt; // alternative using address operator
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 71
Function Pointers
• Comparing Function Pointers
The comparison-operator (==) is used for comparing function
Ex: // comparing function pointers
if(pt2Function == &DoIt)
Printf("pointer points to DoIt\n);"

• Calling a Function using a Function Pointer

There are two alternatives to call a function using a function pointer:


– Use the name of the function pointer instead of the name of the
function.
– Explicitly dereference it.
Ex: // calling a function using a function pointer
int result1 = pt2Function (12, 'a', 'b');
int result2 = (*pt2Function) (12, 'a', 'b');

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 72
Function Pointers
• Returning a Function Pointer
A function pointer can be a function's return value.
float (*GetPtr1(const char opCode))(float, float)
{
if(opCode == '+')
return &Plus;
if(opCode == '-')
return &Minus; }
// using a typedef
typedef float(*pt2Func)(float, float);
pt2Func GetPtr2(const char opCode)
{
if(opCode == '+')
return &Plus;
if(opCode == '-')
return &Minus;
}
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 73
Function Pointers
// execute example code
void Return_A_Function_Pointer()
{
Printf( "Executing 'Return_A_Function_Pointer\n'“);
float (*pt2Function)(float, float); // define a function pointer
pt2Function=GetPtr1('+'); // get function pointer from function
'GetPtr1‘
Printf(pt2Function(2, 4) “\n”); // call function using the pointer
Printf(pt2Function=GetPtr2('-') “\n”); // get function pointer from
function 'GetPtr2‘
Printf(pt2Function(2, 4)”\n”); // call function using the pointer
}

Function pointers are usually used


(a) In writing memory resident programs
(b) In writing viruses or vaccines to remove the viruses
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 74
Function Pointers
• Arrays of Function Pointers
// 2.8 How to Use Arrays of Function Pointers typedef int (*pt2Function)
(float, char, char);
// illustrate how to work with an array of function pointers
Void Array_Of_Function_Pointers()
{
printf("Executing Array_Of_Function_Pointers\n“);
// <funcArr> is an array with 10 pointers to functions which return an int // and
take a float and two char
pt2Function funcArr[10]; // assign the function's address - 'DoIt' and 'DoMore'
are suitable functions // like defined above in 2.1-4
funcArr[0] = &DoIt;
funcArr[1] = &DoMore; /* more assignments */
// calling a function using an index to address the function pointer
printf(funcArr[1](12, 'a', 'b')”\n”);
printf(funcArr[0](12, 'a', 'b')”\n”);
}

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 75
Libraries
• A " library'' is simply a file containing compiled code (and
data) that is to be incorporated later into a program
• A library is a file containing several object files, that can
be used as a single entity in a linking phase of a program.
• Libraries allow programs to be more modular, faster to
recompile, and easier to update.
• Normally the library is indexed, so it is easy to find
symbols (functions, variables and so on) in them.
– For this reason, linking a program whose object files are ordered
in libraries is faster than linking a program whose object files are
separate on the disk.
– Also, when using a library, we have fewer files to look for and
open, which even further speeds up linking.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 76
Libraries
• Libraries can be divided into three types:
– static libraries
• Collections of object files that are linked into the program
during the linking phase of compilation.
– shared libraries
• Linked into the program in two stages.
– First, during compile time, the linker verifies that all the
symbols (functions, variables and the like) required by the
program, are either linked into the program, or in one of its
shared libraries.
– When the program is started, a program in the system
(called a dynamic loader) checks out which shared libraries
were linked with the program, loads them to memory, and
attaches them to the copy of the program in memory
– dynamically loaded (DL) libraries.
• Loaded and used at any time while a program is running
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 77
Static Libraries
• Static libraries are simply a collection of ordinary
object files
• Conventionally, static libraries end with the ``.a''
suffix.
• This collection is created using the ar (archiver)
program.
• Static libraries permit users to link to programs
without having to recompile its code, saving
recompilation time.
• Static libraries are often useful for developers if
they wish to permit programmers to link to their
library, but don't want to give the library source
code
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 78
Static Libraries
• To create a static library, or to add
additional object files to an existing static
library, command to be used is:
– ar rcs my_library.a file1.o file2.o
• Static library can be used by invoking it as part
of the compilation and linking process when
creating a program executable.
• If gcc(1) is used to generate executable, -l option
can be used to specify the link the library

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 79
Shared Libraries
• Shared libraries are libraries that are loaded by
programs when they start.
• When a shared library is installed properly, all
programs that start afterwards automatically use
the new shared library
• Every shared library has a special name called the
``soname''.
• The soname has the prefix ``lib'', the name of the
library, the phrase ``.so'', followed by a period and
a version number that is incremented whenever
the interface changes (as a special exception, the
lowest-level C libraries don't start with ``lib'').
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 80
Shared Libraries
• A fully-qualified soname includes as a prefix the directory
it's in; on a working system a fully-qualified soname is
simply a symbolic link to the shared library's ``real name''.
• /usr/lib/libreadline.so.3 is a fully-qualified soname
• Shared libraries must be placed somewhere in the
filesystem
• The GNU standards recommend installing by default all
libraries in /usr/local/lib
• What's the advantage of creating executables using
Dynamic Libraries? The executable is much smaller than
with static libraries. If it is a standard library that can be
installed, there is no need to compile it into the executable
at compile time!
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 81
Shared Libraries
• Creating shared or dynamic libraries is simple also.
Using the previous example, to create a shared library
gcc -fPIC -c objfile1.c
gcc -fPIC -c objfile2.c
gcc -fPIC -c objfile3.c
gcc -shared -o libmylib.so objfile1.o objfile2.o objfile3.o
• The -fPIC option is to tell the compiler to create Position
Independent Code (create libraries using relative
addresses rather than absolute addresses because
these libraries can be loaded multiple times).
• The -shared option is to specify that an architecture-
dependent shared library is being created.
• However, not all platforms support this flag
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 82
Shared Libraries
• Now we have to compile the actual
program using the libraries:
• gcc -o foo -L. -lmylib foo.o
• Notice it is exactly the same as creating
a static library. Although, it is compiled in
the same way, none of the actual library
code is inserted into the executable,
hence the dynamic/shared library.
• Note: You can automate this process
using Makefiles!
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 83
Dynamic Loaded Libraries
• Dynamically loaded (DL) libraries are
libraries that are loaded at times other than
during the startup of a program. They're
particularly useful for implementing plugins
or modules, because they permit waiting to
load the plugin until it's needed
• They're also useful for implementing
interpreters that wish to occasionally
compile their code into machine code and
use the compiled version for efficiency
purposes, all without stopping
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 84
Libraries
• The key to making your program work with
dynamic libraries is through the
LD_LIBRARY_PATH enviornment variable.
• To display this variable, at a shell: echo
$LD_LIBRARY_PATH Will display this variable if
it is already defined.
• If it isn't, you can create a wrapper script for
your program to set this variable at run-time.
Depending on your shell, simply use setenv
(tcsh, csh) or export (bash, sh, etc) commands.
• If you already have LD_LIBRARY_PATH
defined, make sure you append to the
variable, not overwrite it!

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 85
Libraries
• For example:
• setenv LD_LIBRARY_PATH /path/to/library:$
{LD_LIBRARY_PATH}
• would be the command you would use if you had tcsh/csh and already
had an existing LD_LIBRARY_PATH.
• If you didn't have it already defined, just remove everything right of
the :.
• An example with bash shells:
• export LD_LIBRARY_PATH=/path/to/library:$
{LD_LIBRARY_PATH}
• Again, remove the stuff right of the : and the : itself if you don't
already have an existing LD_LIBRARY_PATH.
• If you have administrative rights to your computer, you can install the
particular library to the /usr/local/lib directory and permanently add an
LD_LIBRARY_PATH into your .tcshrc, .cshrc, .bashrc, etc. file.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 86
Make command

• Make allows a programmer to easily


keep track of a project by maintaining
current versions of their programs from
separate sources.
• Make can automate various tasks for
not only compiling proper branch of
source code from the project tree, but
helping to automate other tasks, such
as cleaning directories, organizing
output, and even debugging.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 87
Make and Makefiles
• Make reads its instructions from text files.
• An initialization file is read first, followed by the makefile.
• The initialization file holds instructions for all “makes” and is used to
customize the operation of Make.
• Make automatically reads the initialization file whenever it starts up.
Typically the initialization file is named make.ini and it resides in the
directory of make.exe and mkmf.exe. The name and location of the
initialization file is discussed in detail on Page .
• The makefile has instructions for a specific project. The default name
of the makefile is literally makefile, but the name can be specified with
a command-line option.
• With a few exceptions, the initialization file holds the same kind of
information as does a makefile. Both the initialization file and the
makefile are composed of the following components: comments,
dependency lines, directives, macros, response files, rules and shell
lines.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 88
Make and Makefiles
Continued Makefile Lines
• Lines in the makefile can be very long. For easier reading a long line can be
broken up by putting “\enter” as the last characters of this line and the rest of this
(logical) line on the next (physical) line of the makefile. For example:
first_part_of_line second_part_of_line
• is the same as:
first_part_of_line \
second_part_of_line
Comments
• The simplest makefile statement is a comment, which is indicated by the
comment character “#”. All text from the comment character to the end of the
line is ignored. Here is a large comment as might appear in a makefile to describe
its contents:
#
# Makefile for Opus Make 6.1
#
# Compiler: Microsoft C 6.0
# Linker: Microsoft Link 5.10
• The comment character could also be used at the end of another makefile
statement:
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 89
some makefile statement # a comment
Make and Makefiles
Comments and Continued Makefile Lines
• If “\enter” appears on a commented line, the comment acts until the end of the line and
the following line is still continued. For example:
line_one \
line_two # more_line_two \
line_three
• is the same as:
line_one line_two line_three  

Rules
• A rule tells Make both when and how to make a file. As an example, suppose your
project involves compiling source files main.c and io.c then linking them to produce the
executable project.exe. Withholding a detailed explanation for a bit, here is a makefile
using Borland C which will manage the task of making project.exe:
• The Example Makefile
project.exe : main.obj io.obj
tlink c0s main.obj io.obj, project.exe,, cs /Lf:\bc\lib
main.obj : main.c
bcc –ms –c main.c
io.obj : io.c
bcc –ms –c io.c
• This makefile shows three rules, one each for making project.exe, main.obj, and io.obj.
The rules as shown above are called explicit rules since they are supplied explicitly in the
makefile. Make also has inference¨˜”°º•Calypso•º°”˜¨
12/07/21
rules that generalize the make process.   90
Make and Makefiles
Dependency Lines: When to Build a Target
• The lines with the colon “:” in them are called dependency
lines. They determine when the target is to be rebuilt.
• To the left of the colon is the target of the dependency. To
the right of the colon are the sources needed to make the
target. A dependency line says “the target depends on the
sources.” For example, the line:
project.exe : main.obj io.obj
• states that project.exe depends on main.obj and io.obj. At
run time Make compares the time that project.exe was last
changed to the times main.obj and io.obj were last changed.
If either source is newer than project.exe, Make rebuilds
project.exe. The last-changed time is the target's time as it
appears in the file-system directory. This time is also known
as the target's timestamp.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 91
Make and Makefiles
The Make Process is Recursive
• It is a basic feature of Make that a target's sources are made before the
timestamp comparison occurs. The line:
project.exe : main.obj io.obj
• implies “make main.obj and io.obj before comparing their timestamps
with project.exe.” In turn:
main.obj : main.c
• says “make main.c before comparing its timestamp with main.obj.”
You can see that if main.c is newer than main.obj, main.obj will be
rebuilt. Now main.obj will be newer than project.exe, causing
project.exe to be rebuilt.
Additional Dependencies
• In C and in other programming languages it is possible to include the
contents of a file into the file currently being compiled. Since the
compiled object depends on the contents of the included file, we add
the included file as a source of the object file.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 92
Make and Makefiles
• Assume each of main.c and io.c include def.h. We can either change
two dependency lines in the makefile:
main.obj : main.c becomes main.obj : main.c def.h
io.obj : io.c becomes io.obj : io.c def.h
• or add a new line which lists only the additional dependencies:
main.obj io.obj : def.h
• Notice that there are two targets on the left of the colon. This line
means that both main.obj and io.obj depend on def.h. Either of these
methods are equivalent. The example makefile now looks like:
project.exe : main.obj io.obj
tlink c0s main.obj io.obj, project.exe,, cs /Lf:\bc\lib
main.obj : main.c
bcc –ms –c main.c
io.obj : io.c
bcc –ms –c io.c
main.obj io.obj : incl.h

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 93
Make and Makefiles
Shell Lines: How to Build a Target [Top]
• The indented lines that follow each dependency line are called shell lines.
Shell lines tell Make how to build the target. For example:
project.exe : main.obj io.obj
tlink c0s main.obj io.obj, project.exe,, cs /Lf:\bc\lib
• tells Make that making project.exe requires running the program tlink to link
main.obj and io.obj. This shell line would be run only if main.obj or io.obj was
newer than project.exe.
• For tlink, c0s is the small model start-up object file and the cs is the small
model library. The /Lf:\bc\lib flag tells tlink that the start-up object file and
library files can be found in the f:\bc\lib directory.
• A target can have more than one shell line, listed one after the other, such as:
project.exe : main.obj io.obj
echo Linking project.exe
tlink c0s main.obj io.obj, project.exe,, cs /Lf:\bc\lib >tlink.out
• The first line shows that command processor commands can be executed by
Make. The second line shows redirection of output, where the output of the
tlink program is redirected to the tlink.out file.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 94
Make and Makefiles
• After each shell line is executed, Make checks the shell line exit
status. By convention, programs return a 0 (zero) exit status if they
finish without error and non-zero if there was an error. The first shell
line returning a non-zero exit status causes Make to display the
message:
OPUS MAKE: Shell line exit status exit_status. Stop.
• This usually means the program being executed failed. Some
programs return a non-zero exit status inappropriately and you can
have Make ignore the exit status by using a shell-line prefix. Prefixes
are characters that appear before the program name and modify the
way Make handles the shell line. For example:
project.exe : main.obj io.obj
– tlink c0s main.obj io.obj, project.exe,, cs /Lf:\bc\lib
• The “–” prefix tells Make to ignore the exit status of shell line. If the
exit status was non-zero Make would display the message:
OPUS MAKE: Shell line exit status exit_status (ignored)  

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 95
Make and Makefiles
Macros
• The example makefile is reproduced here:
project.exe : main.obj io.obj
tlink c0s main.obj io.obj, project.exe,, cs /Lf:\bc\lib
main.obj : main.c
bcc –ms –c main.c
io.obj : io.c
bcc –ms –c io.c
main.obj io.obj : def.h
• We see that the text “main.obj io.obj” occurs repeatedly. To cut down
on the amount of repeated text, we can use a macro definition to assign
a symbol to the text.
Defining Macros in the Makefile
• A macro definition line is a makefile line with a macro name, an
equals sign “=”, and a macro value. In the makefile, expressions of the
form $(name) or ${name} are replaced with value. If the macro name is
a single letter, the parentheses
12/07/21 or braces are optional (i.e. $X, $(X) and
¨˜”°º•Calypso•º°”˜¨ 96
${X} all mean “the value of macro X”).
Make and Makefiles
• Here is the above example written with the introduction of four macros:
OBJS = main.obj io.obj
MODEL = s
CC = bcc
CFLAGS = –m$(MODEL)
project.exe : $(OBJS)
tlink c0$(MODEL) $(OBJS), project.exe,, c$(MODEL) /Lf:\bc\lib
main.obj : main.c
$(CC) $(CFLAGS) –c main.c
io.obj : io.c $(CC) $(CFLAGS) –c io.c
$(OBJS) : incl.h
• The value of the OBJS macro is the list of object files to be compiled.
The macro definitions for MODEL, CC and CFLAGS were introduced
so that it is easier to change the compiler memory model, name of the C
compiler and its options.
• Make automatically imports environment variables as macros, so you
can reference an environment variable such as PATH with the makefile
expression $(PATH).
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 97
Make and Makefiles
Defining Macros on the Command Line
• Macros can be defined on the Make command line. For example:
make CFLAGS=–ms
• would start up Make and define the macro CFLAGS with the value “–
ms”. Macros defined on the command line take precedence over macros
of the same name defined in the makefile.
• If a command-line macro contains spaces, it must be enclosed in double
quotes as in:
make "CFLAGS=-ms -z -p"

Run-Time Macros
• Make defines some special macros whose values are set dynamically.
These macros return information about the current target being built. As
examples, the .TARGET macro is name of the current target, the
.SOURCE macro is the name of the inferred source (from an inference
rule) or the first of the explicit sources and the .SOURCES macro is the
list of all sources.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 98
Make and Makefiles
• Using run-time macros the example can be written:
OBJS = main.obj io.obj
CC = bcc
MODEL = s
CFLAGS = –m$(MODEL)
project.exe : $(OBJS)
tlink c0$(MODEL) $(OBJS), $(.TARGET),, c$(MODEL)
/Lf:\bc\lib
main.obj : main.c
$(CC) $(CFLAGS) –c $(.SOURCE)
io.obj : io.c
$(CC) $(CFLAGS) –c $(.SOURCE)
$(OBJS) : incl.h
• As you can see, the shell lines for updating main.obj and io.obj are
identical when run-time macros are used. Run-time macros are
important for generalizing the build process with inference rules.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 99
Make and Makefiles
Macro Modifiers
• Macros are used to reduce the amount of repeated text. They are also
used in inference rules to generalize the build process. We often want
to start with the value of a macro and modify it in some manner. For
example, to get the list of source files from the OBJS macro we can do:
SRCS = $(OBJS,.obj=.c)
• This example uses the “from=to” macro modifier to replace the from
text in the expansion of OBJS with the to text. The result is that $
(SRCS) is “main.c io.c”. In general, to modify a macro expand it with:
$(name,modifier[,modifier ...])
• Each modifier is applied in succession to the expanded value of name.
Each modifier is separated from the next with a comma.
Filename Components
• There is a set of macro modifiers for accessing parts of file names. For
example, with the macro definition:
SRCS = d:\src\main.c io.asm

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 100


Make and Makefiles
• Some of the modifiers are:
Modifier, and description Example Value
D, the directory $(SRCS,D) d:\src .
E, the extension (or suffix) $(SRCS,E) .c .asm
F, the file name $(SRCS,F) main.c io.asm
Tokenize
• Another modifier is the “Wstr” modifier, which replaces whitespace
between elements of the macro with str, a string. The str can be a mix
of regular characters and special sequences, the most important
sequence being “\n” which represents a newline character (like hitting
the enter key). For example:
$(OBJS,W space +\n) is main.obj +
io.obj
Other Modifiers
• Other modifiers include: “@” (include file contents), “LC” (lower
case), “UC” (upper case), “M” (member) and “N” (non-member). The
“M” and “N” modifiers and the “S” (substitute) modifier use regular
expressions for powerful and flexible pattern-matching. See Page for
more information on all macro modifiers.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 101
Make and Makefiles
Inference Rules
• Inference rules generalize the build process so you don't have to give
an explicit rule for each target. As an example, compiling C source (.c
files) into object files (.obj files) is a common occurrence. Rather than
requiring a statement that each .obj file depends on a like-named .c file,
Make uses an inference rule to infer that dependency. The source
determined by an inference rule is called the inferred source.
• Inference rules are rules distinguished by the use of the character “%”
in the dependency line. The “%” (rule character) is a wild card,
matching zero or more characters. As an example, here is an inference
rule for building .obj files from .c files:
%.obj : %.c
$(CC) $(CFLAGS) –c $(.SOURCE)
• This rule states that a .obj file can be built from a corresponding .c file
with the shell line “$(CC) $(CFLAGS) –c $(.SOURCE)”. The .c and
.obj files share the same root of the file name.
• When the source and target have the same file name except for their
extensions, this rule can be specified in an alternative way:
.c.obj :
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 102
$(CC) $(CFLAGS) –c $(.SOURCE)
Make and Makefiles
• The alternative form is compatible with Opus Make prior to
this version and with other make utilities and is discussed in
more detail on Page .
• Make predefines the “%.obj : %.c” inference rule as listed
above so the example we have been working on now
becomes much simpler:
OBJS = main.obj io.obj
CC = bcc
MODEL = s
CFLAGS = –m$(MODEL)
project.exe : $(OBJS)
tlink c0$(MODEL) $(OBJS), $(.TARGET),, c$(MODEL)
/Lf:\bc\lib
$(OBJS) : incl.h  

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 103


Make and Makefiles
Response Files
• For MS-DOS, OS/2 & Win95 there is a rather severe restriction on the
length of a shell line with the result that the shell line is often too short
for many compilers and far too short for linkers and librarians.
• To overcome this restriction many programs can receive command-line
input from a response file. Opus Make has two kinds of support for
response files: automatic response files, where Make decides when to
build a response file or; inline response files, where you write response
file-creating statements directly in the makefile.
Automatic Response Files
• Make has predefined support for several linkers, librarians and
compilers, and you can augment Make's support by writing your own
definitions. With Make's predefined support you can just add the
following statement to your makefile:
.RESPONSE.LINK : tlink
• This tells Make that the program tlink accepts LINK-style response files.
When a shell line executes tlink, Make checks if the shell line is longer
than allowed by the operating system and automatically produces a
response file if necessary. ¨˜”°º•Calypso•º°”˜¨
12/07/21 104
Make and Makefiles
Inline Response Files
• Response files can also be coded “inline” in your makefile. Here is the
tlink shell line of the example, written to use an inline response file:
project.exe : $(OBJS)
tlink @<<
c0$(MODEL) $(.SOURCES,W+\n)
$(.TARGET)

c$(MODEL) /Lf:\bc\lib
<<
• The tlink program is invoked as “tlink @response_file” where
response_file is a name generated by Make. The “W+\n” macro
modification replaces whitespace between elements of $(.SOURCES)
with “+enter”. The response_file contains:
c0s main.obj+
io.obj
project.exe
c0 /f:\bc\lib  
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 105
Make and Makefiles
Makefile Directives
• Makefile directives control the makefile lines Make reads at read time. Here is our
example extended with conditional directives (%if, %elif, %else and %endif) to
support both Borland and Microsoft compilers. Comments have been added for
documentation:
# This makefile compiles the project listed in the PROJ macro
#
PROJ = project # the name of the project
OBJS = main.obj io.obj # list of object files

# Configuration:
#
MODEL = s # memory model
CC = bcc # name of compiler

# Compiler-dependent section
#
%if $(CC) == bcc # if compiler is bcc
CFLAGS = –m$(MODEL) # $(CFLAGS) is –ms
LDSTART = c0$(MODEL) # the start-up object file
LDLIBS = c$(MODEL) # the library
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 106
LDFLAGS = /Lf:\bc\lib # f:\bc\lib is library directory
Make and Makefiles
%elif $(CC) == cl # else if compiler is cl
CFLAGS = –A$(MODEL,UC) # $(CFLAGS) is –AS
LDSTART = # no special start-up
LDLIBS = # no special library
LDFLAGS = /Lf:\c6\lib; # f:\c6\lib is library directory
%else # else
% abort Unsupported CC==$(CC) # compiler is not supported
%endif # endif

# The project to be built


# $(PROJ).exe : $(OBJS)
tlink $(LDSTART) $(OBJS), $(.TARGET),, $(LDLIBS) $(LDFLAGS)
$(OBJS) : incl.h
• The layout of this makefile is fairly traditional — macros are defined first, the
primary target follows the macros and the extra dependency information is
last.
• This example also uses the %abort directive to abort Make if the makefile
does not support a particular compiler. Directives can also be used at run time,
to control the shell lines Make executes

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 107


Makefiles
• You can include other Makefiles by using the include directive.
• You can create conditional syntax in Makefiles, using ifdef, ifeq, ifndef,
ifneq.
• You can create variables inside of Makefiles, like the $(objects) above.
• Let's use a different example. The hypothetical source tree:
a.c
__/ \__
/ \
b.c c.c
__/__ __\__
/ \ / \
d.c e.h f.c g.c
__ /__ __/__
/ \ / \
h.c <libgen.h> i.c j.h
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 108
Makefiles
• Let's create a more complex, yet easier to maintain Makefile for this
project:
• # Source, Executable, Includes, Library Defines
INCL = e.h j.h
SRC = a.c b.c c.c d.c f.c g.c h.c i.c
OBJ = $(SRC:.c=.o)
LIBS = -lgen
EXE = moolicious
# Compiler, Linker Defines
CC = /usr/bin/gcc
CFLAGS = -ansi -pedantic -Wall -O2
LIBPATH = -L.
LDFLAGS = -o $(EXE) $(LIBPATH) $(LIBS)
CFDEBUG = -ansi -pedantic -Wall -g -DDEBUG $(LDFLAGS)
RM = /bin/rm -f

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 109


Makefiles
# Compile and Assemble C Source Files into Object Files
%.o: %.c
$(CC) -c $(CFLAGS) $*.c
# Link all Object Files with external Libraries into Binaries
$(EXE): $(OBJ)
$(CC) $(LDFLAGS) $(OBJ)
# Objects depend on these Libraries
$(OBJ): $(INCL)
# Create a gdb/dbx Capable Executable with DEBUG flags turned on
debug:
$(CC) $(CFDEBUG) $(SRC)
# Clean Up Objects, Exectuables, Dumps out of source directory clean:
$(RM) $(OBJ) $(EXE) core a.out

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 110


Memory Corruption
SIGSEGV This! SIGBUS That!
• We saw that some virtual memory sections aren't mapped to physical
memory. While some of them were simply paged out, others were
never allocated by the process. When a process runs, its virtual
memory table is small. As it allocates more memory pages, the table
grows. However, if the process tries to access a virtual memory
address of a section it hasn't allocated yet, the operating system has no
where to bring this page from. The designers of the Unix system
decided that this situation indicates a program bug, and thus instead of
making an automatic allocation of a memory page in such a case, they
chose to send a signal to the process. This signal is a SEGV signal (or
SIGSEGV), and its default signal handler prints out a "Segmentation
violation - core dumped" message, and dumps the memory image of
the process into a file named 'core' in the process's current directory.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 111


Memory Corruption
• Another way to cause a 'segmentation violation' is trying to access an
illegal location of virtual memory. Because many invalid pointer
problems occur with very low pointer values, the operating system
does not allow a process to allocate a memory page for the virtual
memory section beginning with the virtual address '0'. This is what
causes programs to receive a SEGV signal when trying to dereference a
NULL pointer (NULL on _most_ machine architectures is defined as
'0').
• What about a BUS (or SIGBUS) signal? this signal is sent to a program
that tries to access a non-aligned pointer. For instance, on many
machine architectures, access to 'long' (4 byte) numbers must be done
using a memory address that divides by 4. Trying to access such an
entity using an address that does not abide by this rule will cause the
CPU to emit a trap. The operating system's kernel catches this trap, and
then sends a BUS signal to the program. The default signal handler for
this signal emits a "Bus error - core dumped" message, and dumps the
memory contents to a 'core' file, much like the handler for the SEGV
signal does.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 112


Debugging
• We will be using three debugging techniques:
1. Non-interactive
2. GNU gdb
3. dbx
• You can debug your code by placing #ifdef DEBUG and corresponding
#endif statements around debug code. For example:
• #ifdef DEBUG
• PRINTF(("Variables Currently Contain: %d, %f, %s\n", *pi, *pf[1],
str));
• #endif
• You can specify a DEBUG define at compile time by issuing gcc with
the -DDEBUG command option.
• Note: This can be even further simplified into a single command called
DPRINTF, so you don't even have to write the #ifdef #endif directives!

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 113


Debugging using gdb
• gdb is a powerful program in tracking down Segmentation Faults and
Core Dumps. It can be used for a variety of debugging purposes
though.
• First thing you must do is compile with the -g option and without any
optimization (i.e. no -O2 flag).
• Once you do that, you can run gdb <exe>. where <exe> is the name of
the executable.
• gdb should load with the executable to run on. Now you can create
breakpoints where you want the the execution to stop. This can be
specified with the line number in the corresponding c source file. For
example: break 376 would instruct gdb to stop at line 376.
• You can now run the program by issuing the run command. If your
program requires command-line options or parameters, you can specify
them with the run command. For example: run 4 -s Doc! where 4, -s,
Doc! are the parameters.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 114


Debugging using gdb
• The program should run until the breakpoint or exit on a failure. If it
fails before the breakpoint you need to re-examine where you should
specify the break. Repeat the breakpoint step and rerun. If your program
stops and shows you the breakpoint line, then you can step into the
function. To step into the function use the step command. NOTE: Do
not step into system library calls (e.g. printf). You can use the command
next over these types of calls or over local function calls you don't wish
to step into. You can repeat the last command by simply pressing enter.
• You can use the continue command to tell gdb to continue executing
until the next breakpoint or it finishes the program.
• If you want to peek at variables, you can issue the print command on
the variable. For example: print mystruct->data.
• You can also set variables using the set command. For example: set
mystruct->data = 42.
• The ptype command can tell you what type a particular variable is.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 115


Debugging using gdb
• The commands instruction tells gdb to set a particular number of
commands and to report them to you. For example, commands 1 will
allow you to enter in a variable number of other commands (one per
line, end it with "end"), and will report those commands to you once
breakpoint 1 is hit.
• The clear command tells gdb to clear a specified breakpoint.
• The list command can tell you where you are at in the particular code
block.
• You can specify breakpoints not only with lines but with function
names.
• For more information on other commands, you can issue the help
command inside gdb.

12/07/21 ¨˜”°º•Calypso•º°”˜¨ 116


Debugging using dbx
• dbx is a multi-threaded program debugger. This program is
great for tracking down memory leaks. dbx is not found on
linux machines (it can be found on Solaris or other *NIX
machines).
• Run dbx with the executable like gdb. Now you can set
arguments with runargs.
• After doing that, issue the check -memuse command. This
will check for memory use. If you want to also check for
access violations, you can use the check -all command.
• Run the program using the run command. If you get any
access violations or memory leaks, dbx will report them to
you.
• Run the help command if you need to understand other
commands or similar gdb commands.
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 117

Você também pode gostar