Memory Management in C
There are two ways in which memory can be allocated in C:
• by declaring variables
• by explicitly requesting space from C
We have discussed variable declaration in other lectures, but here we will describe requesting dynamic memory allocation and memory management.
C provides several functions for memory allocation and management:
• malloc and calloc, to reserve space
• realloc,
to move a reserved block of memory to another allocation of different
dimensions
• free, to release space back to C
These functions can be found in the stdlib library
What happens when a pointer is declared?
Whenever a pointer is
declared, all that happens is that C allocates space for the pointer.
For example,
char *p;
allocates 4
consecutive bytes in memory which are associated with the variable p. p’s
type is declared to be of pointer to char. However, the memory location
occupied by p is not initialised, so it may contain garbage.
It is often a
good idea to initialise the
pointer at the time it is declared, to reduce the chances of a random value in
p to be used as a memory address:
char *p = NULL;
At some stage
during your program you may wish p to point to the location of some string
A common error is to simply copy the required string into p:
strcpy(p, “Hello”);
Often, this will result
in a “Segmentation Fault”. Worse yet, the copy may actually
succeed.
//a.c
#include
<stdio.h>
main() {
char *p;
char *q = NULL;
printf("Address of p = %u\n",
p);
strcpy(p, "Hello");
printf("%s\n", p);
printf("About to copy
\"Goodbye\" to q\n");
strcpy(q, "Goodbye");
printf("String copied\n");
printf("%s\n", q);
}
When p and q are declared, their memory locations contain garbage.
However, the garbage value in p happens to correspond to a memory location that
is not write protected by another process. So the strcpy is permitted.
By initialising q to NULL, we are ensuring that we cannot use q
incorrectly. Trying to copy the string “Goodbye” into location 0
(NULL) results in a run-time Bus Error, and a program crash.
So,
how can we use memory properly?
Before we can use a pointer, it must be pointing to a valid area of
memory. We can use malloc to request a pointer to a block of memory (calloc to
request an array of zero-value initialised blocks).
void *malloc(size_t
byteSize)
void *calloc(size_t
numElems, size_t byteSize)
//b.c
#include
<stdio.h>
#include
<stdlib.h>
main() {
char *q = NULL;
printf("Requesting space for
\"Goodbye\"\n");
q = (char
*)malloc(strlen("Goodbye")+1);
printf("About to copy
\"Goodbye\" to q at address %u\n", q);
strcpy(q, "Goodbye");
printf("String copied\n");
printf("%s\n", q);
}
Malloc (and
calloc) will return a non-NULL value if the request for space has been
successful, and NULL if it fails. Using the result of malloc (or calloc) after
it has failed to locate memory WILL result in a run-time program crash.
//c.c
#include
<stdio.h>
#include
<stdlib.h>
main() {
char *q = NULL;
printf("Requesting space for
\"Goodbye\"\n");
q = (char
*)malloc(strlen("Goodbye")+1);
if (!q) {
perror("Failed to allocate space
because");
exit(1);
}
printf("About to copy
\"Goodbye\" to q at address %u\n", q);
strcpy(q, "Goodbye");
printf("String copied\n");
printf("%s\n", q);
}
The same applies
to reading data from a file. If you use fscanf, etc., you must ensue
that there is enough space to store the input, because the functions
won’t
When space is
allocated using the alloc family of functions, the space is allocated
permanently until the program terminates, or it is freed.
Local variables
are destroyed when their enclosing function terminates. Although the values are
not necessarily overwritten, C may allocate the space to some other requesting
process
//d.c
#include
<stdio.h>
char *foo(char *);
main() {
char *a = NULL;
char *b = NULL;
a = foo("Hi there, Chris");
b = foo("Goodbye");
printf("From main: %s %s\n",
a, b);
}
char *foo(char *p) {
char q[strlen(p)+1];
strcpy(q, p);
printf("From q: the string is
%s\n", q);
return q;
}
In this example, q is a variable local to foo. A string created in main
is passed to foo and copy to q. The address of q is returned to main, where
there is an attempt to preserve and use the strings. The result is disastrous.
//e.c
#include
<stdio.h>
#include
<stdlib.h>
char *foo(char *);
main() {
char *a = NULL;
char *b = NULL;
a = foo("Hi there, Chris");
b = foo("Goodbye");
printf("From main: %s %s\n",
a, b);
}
char *foo(char *p) {
char *q = (char *)malloc(strlen(p)+1);
strcpy(q, p);
printf("From foo: the string is
%s\n", q);
return q;
}
In this example,
however, the space is requested legitimately, and, although q, a local variable
holding an address of the string, is destroyed when foo terminates, the string
itself is preserved and can be used safely in the calling function.
The correct way
to release the space is to use free().
//f.c
#include
<stdio.h>
#include
<stdlib.h>
char *foo(char *);
main() {
char *a = NULL;
char *b = NULL;
a = foo("Hi there, Chris");
free(a);
b = foo("Goodbye");
free(b);
printf("From main: %s %s\n",
a, b);
}
char *foo(char *p) {
char *q = (char *)malloc(strlen(p)+1);
strcpy(q, p);
printf("From foo: the string is
%s\n", q);
return q;
}
If free(b) is
omitted, then “Goodbye” can be seen to be written to the location
of “Hi there, Chris”.
The free
function has the following syntax.
void free(void *ptr)
void *realloc(void
*oldptr, size_t newsize)
You’ve
been accepting characters from the keyboard into some previously allocated
bytes, but the user keeps typing characters and is going to overflow the memory
allocation… what do you do?
What you’d
want to do, of course, is keep track of the number of characters being written,
and when you’re almost out of space, request a larger block from C, copy
the old string into the new location, and free the space associated with the
old string – which is practically what the realloc function does.
//g.c
#include
<stdio.h>
#include
<stdlib.h>
char
*readline(char *, int *);
char
*allocmem(char *, int);
main()
{
char *p = NULL;
int max = 10;
p = (char *)malloc(max);
if
(!p) {
perror("Memory allocation error
1");
exit(1);
}
*p = ‘\0’;
p = readline(p, &max);
printf("User input\n%s\n", p);
}
char
*readline(char *p, int *max) {
char c;
int count = strlen(p);
while ((c = getchar()) != EOF) {
if (count == (*max-1)) {
*(p+(*max-1)) = '\0';
*max += 10;
p = allocmem(p, *max);
if (!p) {
perror("Memory
allocation error 2");
exit(1);
}
}
count+=1;
strncat(p, &c, 1);
}
return p;
}
char
*allocmem(char *p, int max) {
char *q = NULL;
q = (char *)realloc(p, max);
if (!q) {
perror("hi!");
exit(1);
}
return q;
}
Reading keyboard
text and keeping each line input in a linked list…
/*h.c.
The program reads lines of input, and stores each line in a linked list.
Eventually the list is printed */
#include
<stdio.h>
#include
<stdlib.h>
struct
lineList {
char *line;// a line of input
struct lineList *nextLine; // pointer to
the next line
};
//
global variable pointing to head of the linked list
struct
lineList *theHead = NULL;
char
*readline(char *, int *, struct lineList *);
char
*allocmem(char *, int);
struct
lineList *makeElem(char *, struct lineList *);
void
printList(struct lineList *);
main()
{
char *p = NULL;
struct lineList *head = NULL;
int max = 10; // initial size of input
array
extern struct lineList *theHead;
p = (char *)malloc(max); // request
space for the input array
if (!p) {
perror("Memory allocation error
1");
exit(1);
}
*p = '\0'; // we use strlen later, so
initialise input array
p = readline(p, &max, head); // read
all the input data
printList(theHead); // print all the
input data
}
char
*readline(char *p, int *max, struct lineList *elem) {
… // some code has been removed
if (c == '\n') { // if a newline is
encountered in the input
elem = makeElem(p, elem); // copy
the input line (p) to an element
free(p); // we’re going to
resize the input array p, to save space
*max = 10; // set max to 10 again
(same as in main())
p = (char *)malloc(*max); //
request space for the input array
//
NB: the lines from free to here could have been replaced by
//
p = (char *)realloc(p, 10);
//
check that the request was successful (code not shown)
*p = '\0'; // initialise array so
string functions will work
count = 0; // reset count
continue;
}
…
struct
lineList *makeElem(char *p, struct lineList *elem) {
//
add an element to the linked list
//
struct lineList *temp = elem;
struct lineList *head = elem;
extern struct lineList *theHead;
if (!head) { // if the linked list
hasn’t been created yet
//request
space for it
head = (struct lineList
*)malloc(sizeof(struct lineList));
if (!head) {
perror("Couldn't allocate
space for head");
exit(3);
}
theHead = head; // set the global
variable
//request
space to the input line
head->line = (char
*)malloc(strlen(p));
if (!head->line) {
printf("Couldn't allocate
space for %s because", p);
perror("");
exit(4);
}
//
copy the input line to the element
strcpy(head->line, p);
head->nextLine = NULL; // set the
pointer to next element to NULL
return head;
}
//
otherwise, if the linked list exists already
//
look for the last element in the list
while (elem) {
temp = elem;
elem = temp->nextLine;
}
//
create a new element, storing its address in the old last element
temp->nextLine = (struct lineList
*)malloc(sizeof(struct lineList));
if (!temp->nextLine) {
perror("Failed to allocate list
head");
exit(2);
}
//
request space to store the input line in the element
temp->nextLine->line = (char
*)malloc(strlen(p));
if (!temp->nextLine->line) {
printf("Couldn't allocate
space for %s because", p);
perror("");
exit(4);
}
//copy
the input line to the new element
strcpy(temp->nextLine->line, p);
temp->nextLine->nextLine = NULL;
return head;
}
//
print the lines in the linked list, starting from the first element
void
printList(struct lineList *head) {
struct lineList *curr = head;
//
loop while the address of the element is not NULL
//
NULL indicates the end of the linked list
while (curr) {
printf("%s\n", curr->line);
curr = curr->nextLine;
}
}