chapter 0.2 – pointers and memory. type specifiers const may be initialised but not used in any...
TRANSCRIPT
CSCI 3431: OPERATING SYSTEMS
Chapter 0.2 – Pointers and Memory
Type Specifiers const
may be initialised but not used in any subsequent assignment common and useful
volatile lookup the value of the variable on every single access, do not cache,
store in a register, or use in optimisation used for memory mapping implementation dependent; no required semantics sometimes broken
restrict Applied to a pointer indicates that the pointer is the only way to access the contents of the
address improves optimization by preventing pointer aliasing
atomic It's new and I don't really know what it does ... Research suggests it can
be used to somewhat cause a type or function to be atomic with respect to pre-emption
Strings
In C, a string is nothing more than an array of characters.
There is no field to store the length or any other data about the string.
To mark the end of the string, a NULL value (i.e., all bits zero) is used.
String library functions expect there to be a terminating NULL character.
Because a string is an array, I'm going to ignore them when talking about memory and only discuss arrays.
Memory Management
C is nothing but glorified assembly language To program in C effectively, one needs an
understanding of:a. the memory modelb. calling conventions
Three basic ideas:1. Static memory – things that don't change2. Stack memory – things associated with function
calls3. Heap memory – globals, runtime allocation, etc.
Basic Memory Model
The StackEvery function call creates a stack framethat contains:
1. Arguments (values for parameters)2. Return address3. Context (saved register values)4. Local variables
Val/Address Arg 2: EBP + 12
Val/Address Arg 1: EBP + 8
Return Address
Old/Saved EBP
Local Variable 1: EBP - 4
Local Variable 2: EBP - 8
EAX (saved)
…
EDX (saved)
Empty Space
Stack Storage
The stack stores words (i.e., 64 bit values that can fit in a register )
Anything that can't fit on the stack is generally stored in the heap and it's address put on the stack
Thus, we have to know where something is located to know if we have a value or an address
Storage Classes We can somewhat control where things are stored
using storage classes auto: give the variable automatic storage on the
stack; can only be used in functions register: try to keep the variable in a register; may
by ignored; & cannot be used on register variables static
inside functions, the variable retains it value between function calls (heap variable, not stack)
outside functions, it indicates a variable declaration cannot be linked to other compilation units (files)
extern : declare the variable; storage will be defined elsewhere and location determined at link time
Calling Conventions
We put arguments (values for function parameters) on the stack when we call a function.
Call by Value: We are putting a value (e.g., int, float, char) on the stack.
Call by Reference: We are putting the address of the value(s) on the stack. The value is somewhere else (possibly the heap).
Arrays
Arrays are kept in heap storage. The stack only ever stores the
address. A pointer is a variable that stores an
address. To manipulate arrays, we can offset
from the base address using [] notation to obtain a value.
We can also use the pointer directly and we can make pointers for non-arrays if desired.
Syntax
Variables can be declared as pointer by prefixing them with *.int *x;
Pointers can be dereferenced – that is, the value from the stored address is obtained. int z = *x;
The address of something can be obtained by prefixing it with &.x = &argc;
NOTE: It is bad if you take the address of something on the stack that can go away!
Pointers
int x = 5;
int *p;
p = &x;
printf ("%d %d", x, *p);
* := LHS, "is a pointer" := RHS, "take value of" & := "take address of"
Example
int x = 1, y = 2, z[10];
int *ip; /* ip is pointer to integer */
ip = &x; /* ip points to x */
y = *ip; /* y is now 1, the value in x */
*ip = 0; /* x is now 0 */
ip = &z[0]; /* ip points to z[0] */
*ip = y; /* z[0] now 1, the value in y */
Why we need pointers ...
void swap (int x, int y) { int tmp = x; x = y; y = tmp;}
x and y are on the stack and use call by value
This function swaps the values of two stack variables that go away when the function returns causing x, y to be deleted.
Using Pointers
void swap (int *px, int *py) { int tmp = *px; *px = *py; *py = tmp;}
int x = 1, y = 2; swap (&x, &y);
Function Pointers
There are no objects and methods However, we can pass a function
around without using an object We simply pass the address of the
function (since it's in memory as well)
Function names, like array names, are actually addresses
When using a function pointer we need to dereference the address to action the code
Example: Function Pointer
#include <stdio.h>void fun (int a) { printf("Value of a is %d\n", a);} int main () {
/* Define and initialise a function pointer */ void (*fun_ptr)(int) = &fun;
/* Invoke fun() using fun_ptr */ (*fun_ptr)(10); return 0;}
Function Pointersvoid bsearch (
const void *key,const void *base,size_t n, size_t size,int (*cmp)(const void *keyval, const void *datum)
);
char data[256][64];
bsearch("tami",data,64*sizeof(char),256,strcmp);
Notes: void* is typically used to mean "pointer to anything"size_t is the type of the integers that are returned by sizeof
Dynamic Memory Management
We can put things on the heap at runtime
For example we can request heap space into which we can read a file's contents
We use malloc() to do memory allocation
When the memory is no longer needed, we can use free() to release it for re-use
Java provides a "garbage" collector to manage the heap for us ... in C we do it ourselves.
Example: Dynamic Memory
/* Allocate a pair of buffers */char *buf1 = malloc(256 * sizeof (char));char *buf2 = calloc(256, sizeof(char));
/* resize buf1 and “leak” buf2 */buf2 = realloc(buf1, 128 * sizeof (char));
/* Cause a runtime error with a double free */ free(buf1), free(buf2);
Definitions and Declarations Declarations say something exists.
They provide enough information so that the file can be compiled.
Where the declared entity will be located is determined during linking.
The mechanism is in place to support the individual compilation of files and is the basis of "header files" (which mostly store declarations)
Definitions assign memory to something.
Complex Declarations char **argv
pointer to pointer to char int (*daytab)[13]
pointer to array[13] of int int *daytab[13]
array[13] of pointer to int void *comp()
function return pointer to void void (*comp)()
pointer to function returning void
Insane Declarations
char (*(*x())[])() function returning pointer to array[] of
pointers to functions returning char char(*(*x[3])())[5]
array[3] of pointers to functions returning pointer to array[5] of chars
Structures: Records for Data/* Method A: Not using typedef */struct point { int x; int y;};struct point origin = { 0, 0 };
/* Method B: Compressed version of method A */struct point { int x; int y;} origin = { 0, 0 };
/* Method C: Using typedef */typedef struct { int x; int y;} point_t;point_t origin = { 0, 0 };
Recursive Structures
typedef struct node { char *data; struct node next;} node_t;
node_t *list = malloc(sizeof(node_t));list->data = “Meredith”;list->next = NULL;
node_t *list2 = malloc(sizeof(node_t));list2->data = “Tami”;list2->next = list;
Unions
Unions are used to save memory The size of a union is equal to the size of the
largest field It is up to the programmer to use the fields
correctly
union number {
int ival;
float fval;
} n;
n.ival = 1;
printf(“%f\n”, n.fval); /* works, but not a cast */
Do you
?