Compile

gcc hello.ccreates machine language code
a.out loads andexecutes program
great idea in C:
![[Pasted image 20240519124500.png]]

basic grammars

input

1
2
3
4
5
#include <stdio.h>
int main(int argc, char *argv[]) { //
printf("Hello World!\n");
return 0;
}

Combined, argc and argv get the main function to accept arguments.

  • argc will contain the number of strings on the command line (the executable counts as one, plus one for each argument). Here argc is 2:
  • ./a.out myFile
  • argv is a pointer to an array containing the arguments as strings (more later re: pointers and strings).

output

1
2
3
4
5
6
7
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Hello %s!\n", "Numbers!"); // %s: placeholder for the first string
int num = 1234;
printf("Decimal: %d\n", num); // %d: for decimal numeral (十进制整数)
return 0;
}

%X is for hexadecimal (16-dimension) numeral
Everything on your computer is bits.

basic types

Type Description Example
int Integer Numbers (including negatives) 0, 78, -217, 0x7337
unsigned int Unsigned Integers (i.e., non-negatives) 0, 6, 35102
float Floating point decimal 0.0, 3.14159, 6.02e23
double Equal or higher precision floating point 0.0, 3.14159, 6.02e23
char Single character ‘a’, ‘D’, ‘\n’
long Longer int, Size >= sizeof(int), at least 32b 0, 78, -217, 301720971
long long Even longer int, size >= sizeof(long), at least 64b 3170519272109251
boolean: true&false
false:
  • 0 (for ints, all varibles are 0)
  • NULL(pointers)

more features

constants in C

const int a = 1; assigned the 1 to a in the declaration and never change it throughout the program
#define a 1 give 1 to a prior to all of the compilation process

typedef

defining new types are allowed.

1
2
typedef uint8_t BYTE;
BYTE b1, b2;

struct

strurcted objects

1
2
3
4
5
6
7
8
9
10
typedef struct {
int length_in_seconds;
int year_recorded;
} SONG;
SONG song1;
song1.length_in_seconds = 213;
song1.year_recorded = 1994;
SONG song2;
song2.length_in_seconds = 248;
song2.year_recorded = 1988;

control flow

the same as that in Java!

pointers

memory

memory is basically a big single-array.
A pointer refer to the location of the memory.

pointers

1
2
3
4
5
int *p; // tell that the p refer to a pointer to a int memory box
int x = 3;
p = &x; // p = the address of x. & is “address operator” in this context
printf("p points to %d\n", *p); // print the value in the address
*p = 5; // a.k.a. x = 5

usage

Pointers are useful when passing parameters.

1
2
3
4
void addOne (int x) {
x = x + 1;}
int y = 3;
addOne(y); // y is still 3!

good ones:

1
2
3
4
5
6
void addOne (int *p) 
{
*p = *p + 1;
}
int y = 3;
addOne(&y);

common bugs: garbage address

Local variables in C are not inialized!
Therefore, they can be anything.

structs

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct {
int x;
int y;
} Coord;
/* dot notation */
int h = coord1.x;
coord2.y = coord1.y;
int k;
k = (*ptr1).x;
k = ptr1->x; // equivalent
/* This compiles, but what does it do? */
ptr1 = ptr2

arrays

just a big block of memory!
declaration and ini: the same as that of java

pointers for arrays

a[i]*(a+i) they are equal!

pointer changing

wrong code:

1
2
3
4
5
6
void increment_ptr(int32_t *p) 
{ p = p + 1; }
int32_t arr[3] = {50, 60, 70};
int32_t *q = arr;
increment_ptr(q);
printf("*q is %d\n", *q)

printed: q = 50!
remember: C is pass by value!
Instead, pass a pointer to a pointer (“handle”).: Declared as data_type **h.
right code:

1
2
3
4
5
6
void increment_ptr(int32_t **h) 
{ *h = *h + 1; }
int32_t arr[3] = {50, 60, 70};
int32_t *q = arr;
increment_ptr(&q);
printf("*q is %d\n", *q);

common pitfalls

constant using of an number

Wrong

1
2
3
int i, ar[10]; 
for(i = 0; i < 10; i++)
{ ... }

Strongly encouraged

1
2
3
int ARRAY_SIZE = 10; 
int i, a[ARRAY_SIZE];
for(i = 0; i < ARRAY_SIZE; i++){ ...

arrays VS pointers

char *string = char string[]
accessing array elements:
arr[0] is equal to *arr
arr[2] is equal to *(arr+2)

other features

  • array bounds are not checked during the element access!
  • An array is passed into functions as a pointer
  • declared arrays are only allocated while the scope is valid.

Strings

An array of characters,followed by a null terminator. aka \0
C have a branch of functions for string:

  • strlen: computes the length of a string
  • strcpy: copy a string from one to another

endianness

“little endian”: the least significant byte of a value is stored first
“big endian”: contradict to little ones

memory

sizeof(): gives size in bytes of types of varible

address space

  • Stack: local variables inside functions, grows downward
  • Heap: space requested via malloc(); resizes dynamically, grows upward
  • Data (Static Data): variables declared outside main, does not grow or shrink
  • Text (aka code): program executable loaded when program starts, does not change
  • chunk is unwriteable/unreadable so you that crash on NULL pointer access
    ![[Pasted image 20240520110032.png]]
    Programming in C needs to know the memory!
    Global: stored in data frame
    local: stored in the stack, and then released after the function have been used.

stack

every time a function is called, the stack is established.
Last in, first out!

pointers in the stack

It is ok and fine to pause pointers outside into the stack
However, it is bad to return a pointer to sth. in the stack!

heap

dynamic memory–>> the stack!
specify number of memory can be allocated/disallocated

  • malloc(): Allocates raw, uninitialized memory from heap
  • free(): Frees memory on heap
  • ealloc(): Resizes previously allocated heap blocks to new size

malloc()

return void * pointer to blocks of memory
allocate a sturct:

1
2
typedef struct {} TreeNode;
TreeNode *tp = (TreeNode *) malloc (sizeof(TreeNode));

allocate an array of 20 ints:

1
int *ptr = (int *) malloc(20*sizeof(int));

free()

dynamically free the memory

1
2
3
int *ptr = (int *) malloc...
...
free(ptr);

realloc()

resize a previously allocated block at a new size
![[Pasted image 20240521110437.png]]

Memory can be allocated / deallocated at any time!

  • “Memory leak”: If you forget to deallocate memory
  • “Use after free”: If you use data after calling free
  • “Double free”: If you call free 2x on same memory

memory leak

the failure to free()!

use after free

1
2
3
4
struct foo *f;
f = malloc(sizeof(struct foo));
free(f);
bar(f->a); // !!!

double free

1
2
3
struct foo *f = (struct foo *) malloc(10*sizeof(struct foo));
free(f);
free(f); // !!!

forgetting realloc()

realloc() copy the data into a different part of the heap.

more pointers

function pointers

int (* fn) (void * , void * ) = &foo;
 fn is a function that accepts two void * pointers and returns an int and is initially pointing to the function foo.
(* fn)(x, y); will then call the function
example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* map a function onto int array */

void mutate_map(int arr[], int n, int(*fp)(int)) {
for (int i = 0; i < n; i++)
arr[i] = (*fp)(arr[i]);
}
int multiply2 (int x) {
return 2 * x;
}
int multiply10(int x) {
return 10 * x; }

int main() {
int arr[] = {3,1,4}
int n = sizeof(arr)/sizeof(arr[0]);
print_array(arr, n); // 1
mutate_map (arr, n, &multiply2);
print_array(arr, n); // 2
mutate_map (arr, n, &multiply10);
print_array(arr, n); // 3
return 0;
}

generic functions

why? – we want to write general-purpose code
generics:

  • Should generally work regardless of argument type
  • Update blocks of memory, regardless of data type stored in those blocks
    one example:
    ![[Pasted image 20240527174041.png]]

generic memory copying

To access some number of bytes in the memory, we use 2 generics in the String standard library:

  • void *memcpy(void *dest, const void *src, size_t n):
    • copy n bytes from memory area src to memory area dest, return a pointer to dest
  • memmove(void *dest, const void *src, size_t n):
    • copy n bytes from memory area src to the memory area dest, return a pointer to dest.
      use memcpy for performance reasons!

example: swap

consider swap
some swaps:

1
2
3
4
5
void swap_int(int *ptr1, int *ptr2) {
int temp = *ptr1;
*ptr1 = *ptr2;
*ptr2 = temp;
}

consider other swaps: short swap, string swap, char swap…
void* swap can be a “general swap”!

1
2
3
4
5
6
7
8
9
void swap(void *ptr1, void *ptr2, size_t nbytes) {  
// 1. store a copy of data1 in temporary storage
char temp[nbytes];
memcpy(temp, ptr1, nbytes);
// 2. copy data2 to location of data1
memcpy(ptr1, ptr2, nbytes);
// 3. copy data in temporary storage to location of data2
memcpy(ptr2, temp, nbytes);
}

another example: swap_ends

1
2
3
4
void swap(void *ptr1, void *ptr2, size_t nbytes) {…}
void swap_ends(void *arr, size_t nelems, size_t nbytes) {
swap(arr, ??? , nbytes); // (char *) arr + (nelems - 1)*nbytes
}