Escolar Documentos
Profissional Documentos
Cultura Documentos
-¨˜”°º•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
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 5
Memory Management
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);
12/07/21 ¨˜”°º•Calypso•º°”˜¨ 16
Memory Management
Assigning memory to segments
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
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
• 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;
• And:
int integernumber=10;
float floatnumber;
floatnumber=(float)integernumber;
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.
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
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);"
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
}
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
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
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