0% found this document useful (0 votes)
5 views19 pages

C Programming 1

The document provides an overview of C programming, focusing on memory management, data types, and pointers. It emphasizes the importance of understanding memory allocation, the special role of zero, and how strings and arrays function in C. Additionally, it covers the use of pointers for dynamic memory management and function pointers, along with the concepts of big-endian and little-endian memory representation.

Uploaded by

ts24bnh8g7
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views19 pages

C Programming 1

The document provides an overview of C programming, focusing on memory management, data types, and pointers. It emphasizes the importance of understanding memory allocation, the special role of zero, and how strings and arrays function in C. Additionally, it covers the use of pointers for dynamic memory management and function pointers, along with the concepts of big-endian and little-endian memory representation.

Uploaded by

ts24bnh8g7
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 19

C Programming

The Basics
C retains the basic philosophy that
programmers know what they are
doing; it only requires that they state
their intentions explicitly.
B Kernighan & D Ritchie, The C Programming Language, 1988
Three simple rules

1. All data has a place (in memory);


2. All data has a size (in bytes);
3. Zero is special.

If you violate any of the C rules, you end up with undefined behaviour:

“behaviour, upon use of a nonportable or erroneous program construct or of erroneous


data, for which this International Standard imposes no requirements.”
Memory management

● A memory-managed language manages dynamic memory allocation/deallocation for you.


● Static memory allocation is known at compile-time
○ Example: size of an integer, or size of a fixed-size array
● Dynamic memory allocation is not known at compile-time
○ Example: reading a list of n integers, where n is specified at runtime

C is not a memory-managed language.

You will manage the dynamic memory allocation/deallocation yourself!


Memory (“all data has a place”)

Data must be located in precisely one of these three places:

● The stack.
○ Read-write, created during function-call setup, destroyed during function-call teardown.
○ Only possible to store data where storage size is known at compile-time.
● The heap.
○ Read-write, allocated from the operating system by a syscall, returned to the operating system by a syscall.
○ Can store data where storage size is storage size is unknown at compile-time.
● The data segment (or other in-process section).
○ Read-only, allocated within the process, stores literals and constants and so on.
Memory (“all data has a size”)

The type of data determines its size.

By definition, a char in C has the storage size of 1. In practice, this is equivalent to a byte.

The sizes of all other types are given in terms of char-units.

● On a typical x64 machine: int = 4, long = 8, short = 2, float = 4, double = 8


● Numeric types (including char) can be signed or unsigned. This has no effect on their size.
● To get the size of a type or variable, use the sizeof operator: sizeof(v) or sizeof(int)
○ This is returned as an unsigned numeric type called size_t , which is usually just another name for “unsigned
int” or “unsigned long”
● You can use casting to force C to treat a type X as if it was type Y.
○ This trades type-safety for convenience and flexibility.
Memory (“all data has a place”)

If you have a variable v, it must have some address (i.e. location) in memory.

● To get the address of v, use the address-of operator: &v


● The address is the memory location where that data starts.

If you have a memory address m, and you know the type of the value at that memory address, then

● To use the data stored at m, use the dereferencing operator: *m


● Pointer arithmetic can be used to look at multiple values, with the same type, that are stored
sequentially in memory (e.g. in an “array”).
○ Pointer-arithmetic notation examples: *(m+1) or *(m+2) or *(m + n)
○ Pointer-arithmetic moves the memory location in units of type size.
Terminology: “pointer”

A variable that stores a memory address is called a pointer (because it can “point” to other data which is at
the stored memory address).

● All pointers have the same size


○ The size of a pointer depends on the maximum memory that can be addressed on a machine
○ On an x64 machine, all pointers have a size of 8 (can you see why?).
● Pointers can point to different types
○ e.g. we say that an int* is a “pointer to integer” or “int pointer”
○ You can also have a pointer to a pointer!
● A pointer which just points to a memory address, without a type, is called a “void pointer” or
“pointer to void” and has the type void*.
● When the address-of operator & is used with any variable, the result must logically be a pointer
which has that type.
Creating a pointer variable
First, declare a variable as a pointer by prefixing the declaration with *. Example: int *k declares a pointer
with type int. Understand this as: k stores the memory address of an integer.

Then you can do these operations with it:

● Assignment: change the memory address that is stored.


● Dereferencing: get the value at the stored address by using the dereferencing operator *.
○ This is sometimes called “following” a pointer. Example: *k
● Pointer arithmetic: add or subtract from the stored address to get to another address.
○ The size of the pointer type is used to calculate the new address. Example: if k is …19010, then k+1 is …19014.
● Indexing: do pointer arithmetic and then dereference, using indexing notation.
○ Example: if k is ...19010, then k[0] will return the integer at …19010 and k[1] will return the integer at …19014.
● Address-of: get the address of the pointer itself by using the address-of operator.
○ This will be a pointer to the type of the pointer. Example: &k will be an int**.
void*
You can declare a pointer without a type by specifying void as the type. Example: void *p declares a
pointer without a type.

A void pointer just holds a memory location.

● You cannot use indexing or pointer arithmetic on it, because there is no way of knowing the size of
the data that it points to.
● You cannot dereference it, because there is no way of knowing how to interpret the memory that it
points to.

However, you can cast to another pointer type and then do either of the above with it.
Example: *((int*)p) casts to a pointer-to-int, and then dereferences that.
“Zero is special”
A zero is special because:

● When used in a conditional, all non-zero values are “true”, and zero is false.
● Zero is conventionally placed at the end of things with an unknown or dynamic size.
○ When used in this way, it means: “stop reading the memory now, the data has ended”.
○ A zero, used in this way, is called a terminator.
● (…where have you seen this “zero is special” idea before?)

Zero is so special that all built-in types have their own version of zero!

● '\0' is the char that means “zero”.


● 0 is the numeric type (int / char / float / double / etc) that means “zero”.
● NULL is the pointer value that means “zero”.
Strings in C

A “string”, in C, is just a sequence of NULL-terminated char values.

Just like in assembly, string constants are stored in the data segment. This part of memory is read-only.
(you can try to write to it, but that is undefined behaviour)

Example: char *r = "testing"; will create space on the stack for a pointer-to-character named r, which
holds the address of a place in memory where 't', 'e', …, 'g', and '\0' are sequentially laid out.
Memory (arrays)

You can create arrays of a type. Features of arrays:

1. Arrays are allocated on the stack.


2. An array must be given a size during array creation, either explicitly or implicitly.
○ Explicit size example: int a[3] is an array with length 3.
○ Implicit size example: double b[] = { 9.5, 87.2, -32.6, 5.0 } is an array with length 4.
3. Array elements are laid out sequentially in memory.
4. You can use indexing notation or pointer arithmetic with an array.
5. The sizeof operator will give you the total size of the array.
○ This is different from what sizeof tells you about pointers!
6. The number of elements in an array becomes unknown when an array is passed to a function.
○ The array itself isn’t passed: a pointer to the starting element of that array is passed.
○ We say that an array decays into a pointer when it is passed around.
○ C will treat an array as a pointer in many cases, so an array can often be used as if it was a pointer.
Use of pointers (1 / 5)
Pointers (i.e. variables that hold a memory location) are used everywhere!

A pointer can point to the first character of a string, and we can assume that all subsequent values—until
we see a '\0'—are part of that string. Instead of copying around a whole string, we just copy the pointer
to the start of it!

● In fact, this is exactly what a C-style string is: just a bunch of memory with a terminator at the end
of it.
● We can even initialize a char array implicitly with a string, e.g. char y[] = "testing" . At
compile-time, C will allocate 8 bytes to the y array and initialize it as if you wrote char y[] = { 't', 'e',
's', 't', 'i', 'n', 'g', '\0' } instead.
● If passing a non-char array, it is much more common to specify the size as an additional parameter.
Use of pointers (2 / 5)
Pointers (i.e. variables that hold a memory location) are used everywhere!

We can ask the operating system for memory of a certain size, and we can store the address of the start
of that memory in a pointer. We can then give that memory back to the operating system when we’re
done with it. We request memory with functions from <stdlib.h>.

● Use malloc to request memory from the OS


● Use calloc to request memory that is set to zero from the OS
● Use realloc to “resize” a previous memory allocation
○ In reality: this copies the data from the previous memory allocation to a new block, then returns a pointer to
the new block
● Use free to give memory back to the OS
Use of pointers (4 / 5)
Pointers (i.e. variables that hold a memory location) are used everywhere!

Our pointer can store the address of a function, and we can then call the function using the pointer 🤯.

● A function to pointer looks something like: int (*f)(char, char). This declares f as a pointer to
function taking parameters (char, char) and returning an int.
Use of pointers (5 / 5)
Pointers (i.e. variables that hold a memory location) are used everywhere!

Our pointer can store the memory address of another pointer! That lets us create multidimensional
arrays 🤯.

● You can write char **m to have a pointer to pointer to char—or, as I might call it, an array of strings.
● You can write int **xs to have a pointer to pointer of integers—or a table/matrix.
● You must allocate memory for each dimension manually.
Big-endian vs Little-endian

Example: let’s say that you have the following statement in C:

int v = 123456789;

What might this look like in memory?

123456789 (in decimal) = 75bcd15 (in hexadecimal). In bytes, that is: 7 5b cd 15.

Value (in hex) 15 CD 5B 7

Memory address (in hex) …6c9578 …6c9579 …6c957a …6c957b

In C v
Big-endian vs Little-endian

Things to note:

● &v is …6c9578
● sizeof(v) is 4
● Memory addresses are in char-units (i.e. bytes)
● When we know the address and we know the size, we also know exactly where it ends.

Value (in hex) 15 CD 5B 7

Memory address (in hex) …6c9578 …6c9579 …6c957a …6c957b

In C v

You might also like