ECE 209: Heap

Typical segment layout for C

Text Contains the compiled code of the main program, its subroutines, and any static libraries. Fixed size
Data Often considered to have three parts: (1) The data area contains initialized variables with static duration. (2) The BSS area contains uninitialized variables with static duration. (3) The heap contains data allocated and freed explicitly by the running program. Data and BSS areas are fixed size. Heap can grow and shrink.
Stack Contains variables of automatic duration. Grows with function calls. Shrinks with function returns.

Views of memory allocation

The real world

Memory allocation in C

Example of dynamic memory use

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

int main(int argc, char** argv) {
    int numberToReverse ;
    printf("How many to reverse?\n") ;
    while (scanf("%d", &numberToReverse)==1
            && numberToReverse > 0) {
        int i ;
        int *ReverseThese ;
        if ((ReverseThese=(int *)malloc(numberToReverse*sizeof(int)))
                == NULL) {
            printf("Too big\n") ;
            return(EXIT_FAILURE) ;
        }
        for(i=0; i < numberToReverse; ++i) {
            printf("Enter number %4d\n", i) ;
            scanf("%d", &ReverseThese[i]) ;
        }
        for(i=numberToReverse-1; i >= 0; --i) {
            printf("Number %4d: %8d\n", i, ReverseThese[i]) ;
        }
        free(ReverseThese) ;
    }
    return (EXIT_SUCCESS);
}

Safe and unsafe dynamic memory use

Type qualifiers

C has three type qualifiers whose most like use will be with pointers. The qualifiers may be attached to a declared variable or to lvalues based on that variable. For example, if p is declared as a pointer to an integer, then the qualifier may be attached to either p or *p. For example, the following declarations prevent assignments to p and *q. However, assignments may be made to *p and q.

int * const p ;
const int * q ;

const

The const qualifier was added in the C89 standard. The const qualifier insures that no modifications are made to a variable through a C lvalue.

In some instances declaring a variable as const may allow a compiler to generate more efficient code, but the main use of const is to declare useful program constants or to indicate that a procedure will not modify data accessed through a passed pointer.

The following prototype declaration assures us that strncpy will not use the source parameter to modify the string referenced by source.

char *strncpy(char *destination, const char *source, size_t n) ;

However, if you pass overlapping pointers to strcpy, all bets are off.

volatile

The volatile qualifier was also added in the C89 standard. The volatile qualifier is rarely seen in the application code. It is used to indicate that an lvalue can be changed by some outside agent, such as a device or a sibling thread, a concurrently executing C program sharing a common set of variables.

The volatile qualifier prevents a compiler from making otherwise sensible optimizations. For example, in the following sequence of code, where p is declared as a pointer to an integer, it seems obvious that x will be assigned the value 5 and the "smart" compiler should just generate code that assigns 5 to x without dereferencing p.

*p = 5;
/* Do nothing for a million increments of i */
for (int i=0; i<1000000; ++i) { }
x = *p ;

However, if *p was defined a volatile, the compiler would know that the value of *p could be changed by something other than the for between the writing and reading of *p and would generate machine code to dereference p after the loop.

Memory mapped I/O

The volatile declarations are needed in the following code because they refer to data that can be modified by memory-mapped I/O devices.

/* Based on the Keyboard Echo example from Patt and Patel (p. 207) */
volatile int * KBSR = (int *) 0xFE00 ;
volatile int * KBDR = (int *) 0xFE02 ;
volatile int * DSR  = (int *) 0xFE04 ;
volatile int * DDR  = (int *) 0xFE06 ;
int NewChar ;

while (*KBSR > 0) /*spin*/ ;
NewChar = *KBDR ;

while (*DSR  > 0) /*spin*/ ;
*DDR  = NewChar ;

restrict

The restrict qualifier wasn't added to C until the C99 standard. The restrict qualifier is very difficult to explain (and understand) and can only be applied to pointers, but the basic idea is that the restricted pointer does not "share" referenced "objects" with other pointers.

The most common use of restrict is with function parameters. Consider the following C function with restricted parameters.

void multiplyVec(double * restrict X, double * restrict Y, int n) {
  *X = *X * n ;
  *Y = *Y * n ;
}

Normally, the C compiler could not schedule the two multiplications to be preformed concurrently, because X and Y might point to the same double. However, with restrict the compiler knows that there is no sharing between the pointers, and it can generate faster code.

Compiler enforcement

However, don't expect the compiler to complain if someone writes multiplyVec(&A, &A, 100). Just to illustrate how difficult it is to enforce these rules on calls consider calls to the strncpy function.

char *strncpy(char * restrict dest, const char * restrict src, size_t n) ;

The purpose of the restrict is to prevent the two strings from overlapping. This means that the following call confirms to the restrict if i is 3 or less but doesn't otherwise. It is impossible for a compiler to always determine if i will satisfy this rule.

strncpy(&c[3], &c[0], i)