Quick C
Remember pointers always hold an address so if you’re reading some spec,
replace pointer with address if it gets confusing.
function bleh that takes a pointer can be read as this function is directly accessing or modifying the object
1 Basics
char *str = "Hello";
+----+----+---+---+---+---+---+----+----+----+
Memory: | .. | .. | H | e | l | l | o | \0 | .. | .. |
+----+----+---+---+---+---+---+----+----+----+
^ str
standard library is nested in angled brackets like <iostream> <stdio.h>
#include <stdio.h>
int b = 5;
int *a = &b;
("address of pointee b aka value of a is %p \n", a);
printf("value of pointee is b \n", *(int*)(a));
printf//address of pointee b aka value of a is 0xbefe3588
//value of pointee b is 5
("An int is %d, A long is %ld, a float is %f, a double is %lf, a char is %c, a string is %s, a pointer is %p",12,25555555,23.1,23.01,'c',"asd",a) printf
printf | keywords |
---|---|
%c | char |
%s | string |
%d,%ld | int,long |
%f | float |
%lf | double |
%p | pointer value aka address of pointee |
%x | convert unsigned number to hex |
%o | convert unsigned number to octal |
2 MACROS
#ifndef two
#define two 2
#endif
int a = two;
ifndef tells us if not defined then define it else skip it.
#define MAX(a,b) a > b ? a : b
3 Example linking multiple object files
#include <stdio.h>
#include "helper.h"
#ifndef two
#define two 2
#endif
int main(){
int a = two;
("hey %d ", plus(a,3));
printfreturn 0;
}
#ifndef avoidRepeatImport
#define avoidRepeatImport
int plus(int a, int b);
#endif
#include "helper.h"
int plus(int a, int b){
return a + b;
}
Below, the “-c” flag tells us to convert helper.c to helper.o
gcc -c helper.c #outputs helper.o
gcc -c main.c #outputs main.o
gcc main.o helper.o -o myexecutable
./myexecutable
Alternatively we can just do
gcc main.c helper.c -o myexecutable -Wall
./myexecutable
4 makefile
- “make” is analogous to “npm build”
- “makefile” is analogous to “package.json”
On Vscode bottom right click on Spaces and turn to tab.
make requires tab before gcc command.
cat -e -t -v makefile #Use to check for tabs ^I
make #this will build the makefile
all: run
gcc main.o
./a.out
run: main.c
gcc -c main.c
clean:
rm a.out main.o
all: main.o helper.o
gcc main.o helper.o -o myexecutable
main.o: main.c helper.h
gcc -c main.c
helper.o: helper.c helper.h
gcc -c helper.c
clean:
rm helper.o main.o myexecutable
- the all keyword will always run.
- all: main.o …
- therefore the make will go to
main.o main.o: <Dependency1> <Dependency2>…
5 null pointers, structs
- uninitialized base variables and pointer are both
NULL
when read - uninitalized struct are also
NULL
when read - but members of uninitalized struct variables have random value and addresses(if the member is a pointer)
- but members of uninitalized struct pointers always give seg fault
struct ll {
int var;
struct* ll next;
}
struct ll newlist;
("%d",newlist); // 0
printf("%p",newlistptr); // (nil)
printf
("%d",newlist.next); // 1824314
printf("%p",newlistptr.next); // 0x294452u205
printf
struct ll* newlistptr;
("%d",newlistptr); // 0
printf("%p",newlistptr); // (nil)
printf
("%d",(*newlistptr).next); // segfault
printf("%p",(*newlistptr).next); // segfault printf
6 Strings
- String is just a pointer to a character
char *str;
= "name" str
"name"
is actually the address of the beginning of the array['n','a','m','e','\0']
- this means
str
points to the first character in'n'
in the array
- this means
6.0.0.1 String literals are immutable in memory
- Typing a string like “hello” in your code editor means your progam will allocate a permanent address and memory for “hello” that all other “hello” in your code will refer to.
("%s","hello");
printf//init string literal "hello" at SOME address
//0xfffeee -> "hello"
("%s","hello");
printf//REUSE same string literal at SAME address
//0xfffeee -> "hello"
6.0.0.2 Different ways to init String
char s[256];
//MUTABLE array of 256 initialized.
char s[256] = "hello";
[1]='Y';
s//RHS: immutable string literal "hello" is initialized
//LHS: MUTABLE array s of 256 initialized.
//Copies string( and Auto appends a terminating \0)
//RESULT: "hello\0" MUTABLE
char s[] = "hello";
[1]='Y';
s//RHS: immutable string literal "hello" is initialized
//LHS: automatically initialized 8 bytes MUTABLE array s.
//Copies string(and Auto append a terminating \0)
//RESULT: "hello\0" MUTABLE
char *s = "hello";
*(s+1)='Y'; //Crash trying to change 'e' => 'Y'
//RHS: immutable string literal "hello" is initialized
//LHS: s POINTS to IMMUTABLE string
//We Crash for trying to modify an immutable string literal
6.0.1 Dynamic mem allocation
malloc(size_t x) allocates x bytes of heap memory return address for x return a void pointer, void int a; a = malloc(5 * sizeof(int));
free(void * p) release dynamically allocated memory input: pointer[aka address] to the beginning of a dynamically allocated block of memory
calloc(size_t n, size_t x) basically just malloc with n*x bytes of memory
realloc(void *p, size_t x) p must point to beginning of block reallocate memory to x size.
GDB must compile with -g gcc -g … gdb someprogram
run
6.1 file functions
<fcntl.h> open add a . file to file table returns file descriptor if open fails => -1
open(path,flags, mode) * mode - basically file permission like 777 * used in file creation * flags - what to do with file * O_RDONLY * O_WRONLY * O_RDWR * O_CREAT
<sys/stat.h> umask set file creation for all files; umask(777); //gives all new file that will be created full permission
<unistd.h> read data from file read(filename, buffer , size) read size bytes from filename into buffer
<unistd.h> write(filename,buffer,size)
<unistd.h> lseek set current position in open file lseek(file_descriptor, offset,whence) * offset - number of bytes to move position by, (-inf,inf) * whence - where to measure offset from * SEEK_SET - beginning of file * SEEK_CUR - current position in file * SEEK)END - end of file
<sys/stat.h> stat(path , stat_buffer ) get information about a file
6.2 DIRECTORIES
<dirent.h> opendir Open a directory file opendir( path); returns a pointer to a directory stream typed “DIR ”
closedir(dir_stream) closes directory stream and frees the pointer
readdir(dir_stream) returns pointer to next entry in a directory stream or NULL if all entries have already been returned.
rewinddir(DIR * d)
resets directory stream of d to the beginngin
INPUT OUTPUT int main( int argc, char *argv[] ) argc - number of command line args argv - array of command line args as strings
stdin input <stdio.h> fgets(char * s, int size, FILE * f) reads size-1 characters fomr file stream f and stores it in s and appends NULL. stops at newline, EOF. File stream has type “FILE ” more complex than file descriptor stdin is a universal FILE variable fgets(s,100 stdin);
6.3 Signals
kill(pid,signal)
sighandler is basically a callback function you define. static void sighandler (int signo ) intercept signals in a c program, cannot catch SIGKILL,SIGSTOP
signal(SIGNUMBER, sighandler) attach a signal to a signal handling function
static void sighandler(int signo){ if (signo == SIGUSR1 ) printf(“YO”); } signal(SIGUSR1,sighandler);
Exec - c functions that run other proc or replace current proc with anothe proc <unistd.h>
execl(path, command ,arg0, arg1 .. )
execlp(path, command, arg0,arg1 ..) same as execlp but uses $PATH environment variables
Managing Sub-processes
<unistd.h> fork()
<sys/wait.h> wait(status) stops parent process until child exited return pid of child exited * status * WIFEEXITED - true if exit normally * WEXITSTATUS - return value of child * WIFSIGNALED - true if exit with signal * WTERMSIG - signal number intercepted dby child
waidpid(pid,status,options) wait for specific child
6.4 Redirection
<unistd.h>
(fd1,fd2) dup2
- redirects fd2 to fd1
6.5 Pipes
A conduit between 2 separate processes on the same computer.
Pipes have 2 ends, a read end and a write end.
Pipes act just like files (i.e. you can read() and write() to send any kind of data).
Pipes exist in memory.
6.5.1 Unnamed Pipes
Unnamed pipes have no external identifier.
pipe - <unistd.h>
Create an unnamed pipe.
Returns 0 if the pipe was created, -1 if not.
6.5.2 pipe( descriptors )
descriptors
Array that will contain the descriptors for each end of the pipe. Must be an int array of size 2.
descriptors[0] is the read end.
descriptors[1] is the write end.
int fds[2];
( fds );
pipechar line[100];
= fork();
f if (f == 0) {
( fds[0] );
close( fds[1], "hello!", 7);
write}
else {
( fds[1] );
close( fds[0], line, sizeof(line) );
read}
6.5.3 Named Pipes
Also known as FIFOs.
Same as unnamed pipes except FIFOs have a name that can be used to identify them via different programs.
Like unnamed pipes, FIFOS are unidirectional.
6.5.4 mkfifo
Shell command to make a FIFO
$ mkfifo name
mkfifo - <sys/types.h> <sys/stat.h>
c function to create a FIFO
Returns 0 on success and -1 on failure
Once created, the FIFO acts like a regular file, and we can use open, read, write, and close on it.
FIFOs will block on open until both ends of the pipe have a connection.
mkfifo( name, permissions )
7 Assembly MOV vs LEA
lea just mov but don’t dereference the source argument
mov eax, var == lea eax, [var] ; i.e. mov r32, imm32
lea eax, [var+16] == mov eax, var+16 lea eax, [eax*4] == shl eax, 2 ; but without setting flags
8 C-string vs Preprocessor string
void square() {
char hello[] = "WORLD";
( hello);
puts}
square:
//function call pushes stuff to stackframe
push rbp
mov rbp, rsp
sub rsp, 16
//char hello[] = "WORLD";
mov DWORD PTR [rbp-6], 1280462679
mov WORD PTR [rbp-2], 68
//puts(hello);
lea rax, [rbp-6]
mov rdi, rax
call puts
//exit function
nop
leave ret
mov DWORD PTR [rbp-6], 1280462679
Hex(1280462679) = 0x4C524F57
Split up 0x4C524F57 every 2 hex: 4C 52 4F 57
Translate 4C 52 4F 57 to ASCII: L R O W
mov WORD PTR [rbp-2], 68
Hex(68) = 0x44
Translate 0x44 to ASCII: D
Higher Address
+------------------------+
| [rbp-2] | <- WORD (2 bytes) containing ASCII 'D' (68 in hex)
+------------------------+
| [rbp-6] | <- DWORD (4 bytes) containing ASCII 'LROW' (1280462679 in hex)
+------------------------+
| ... | <- Other stack data
| ... | Lower Address
WORLD spelled backwards, since it is pushed into the stack
Below uses a preprocessor string
#define hello "WORLD"
void square() {
(hello);
puts}
.LC0:
.string "WORLD"
square:
//function call pushes stuff to stackframe
push rbp
mov rbp, rsp
//preprocessor #define hello "WORLD"
mov edi, OFFSET FLAT:.LC0
//puts(hello);
call puts
//exit function
nop
pop rbp ret
We see that the variable allocated char[] takes spaces of rdi
while the proprocessor allocated string takes space of edi
9 fork example
#include <stdio.h>
int main(){
int commonVar = getpid();
int a= 5;
int *b = &a;
("%d\n",commonVar);
printf// sleep(100);
int soul = fork();
if(soul == 0){
= a *3;
a *b = *b * 2;
("commonVar: %d\n",commonVar);
printf("pid: %d\n",getpid());
printf("a:%d\n", a);
printfreturn 0;
}
if(soul != 0){
= a +1;
a *b = *b * 4;
("commonVar: %d\n",commonVar);
printf("pid: %d\n",getpid());
printf("a:%d\n", a);
printfreturn 0;
}
return 0;
}
// 2442774
// commonVar: 2442774
// pid: 2442774
// a:6
// commonVar: 2442774
// pid: 2442775
// a:15
//Notice when the process is forked, everything gets duplicated
10 pthread Example
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
void* func(void* x) {
int xi = (int)x;
("Inside thread; x = %d\n", xi);
printf
return (void*)(xi + 123);
}
int main(int argc, char** argv) {
;
pthread_t th(&th, NULL, func, (void*)100);
pthread_create
void* ret_from_thread;
int ri;
(th, &ret_from_thread);
pthread_join= (int)ret_from_thread;
ri
("Outside thread, which returned %d\n", ri);
printfreturn 0;
}
11 Write to file example
#include <stdio.h>
#include <stdlib.h> //calloc
#include <unistd.h> //POSIX write
#include <fcntl.h> //open
size_t strlen(const char *s){
size_t cnt = 0 ;
while( s[cnt] != '\0'){
++;
cnt}
return cnt;
}
void writeTerminal(){
//printf prins AFTER the `hey` buf gets printed
//Because printf is BUFFERED
("Writing directly to stdout printed last\n");
printfchar buf[4];
[0] = 'h';
buf[1] = 'e';
buf[2] = 'y';
buf[3] = '\n';
buf//write is POSIX, fwrite is for all OS
(1,buf,4); //1 means stdout
write(2,buf,4);
write(0,buf,4);
write(3,buf,4);
write}
void writeNreadFile(){
("Writing to file\n");
printfchar *buf = (char*) calloc(100, sizeof(char)); //calloc is just malloc but zero'd out
//! WRITING TO FILE
int fhandle;
= open("bleh.txt",O_RDWR | O_CREAT);
fhandle ("%p\n",fhandle);
printfint response = write(fhandle, "hilo\n", strlen("hilo"));
(fhandle);
close
//! READING TO FILE
}
void writeNreadFile2(){
char *buf = (char*) calloc(100, sizeof(char)); //calloc is just malloc but zero'd out
FILE* fhandle;
= fopen("bleh2.txt", "w+");
fhandle ("%d\n",*fhandle);
printf(fhandle,"this is content");
fprintf(fhandle);
fclose
}
void readRandom(){
int randFile = open("/dev/random", O_RDONLY);
unsigned char s;
(randFile, &s, sizeof(char));
read("%d\n",s);
printf(randFile);
close
}
//!write will printout
int main(){
();
writeTerminal();
writeNreadFile();
writeNreadFile2();
readRandom}
12 Deadlock example
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
void* func2(void* x) {
int xi = (int)x;
("Inside thread; x = %d\n", xi);
printf(1);
sleepreturn (void*)(xi + 1233);
}
void* func(void* x) {
int xi = (int)x;
("Inside thread; x = %d\n", xi);
printf
return (void*)(xi + 123);
}
int main(int argc, char** argv) {
;
pthread_t th;
pthread_t th2(&th, NULL, func, (void*)100);
pthread_create(&th2, NULL, func2, (void*)100);
pthread_createvoid* ret_from_thread;
int ri;
(th, &ret_from_thread);
pthread_join(th2, &ret_from_thread);
pthread_join= (int)ret_from_thread;
ri
("Outside thread, which returned %d\n", ri);
printfreturn 0;
}
12.1 list all files in directory
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
void listFiles(const char *basePath, const char *relativePath) {
char path[1024];
struct dirent *entry;
*dir = opendir(basePath);
DIR
if (!dir) {
("Error opening directory");
perror(EXIT_FAILURE);
exit}
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.')
continue; // Skip "." and ".."
(path, sizeof(path), "%s/%s", basePath, entry->d_name);
snprintf
if (entry->d_type == DT_DIR) {
// It's a directory, recursively list its contents
(path, entry->d_name);
listFiles} else {
// It's a file, print its relative path
("%s/%s\n", relativePath, entry->d_name);
printf}
}
(dir);
closedir}
int main(int argc, char**argv) {
const char *directoryPath = argv[1];
// Call the function to list files
(directoryPath,"");
listFiles// for(int i = 0; i < n; i++){
// printf("%s\n",stack[i]);
// }
return 0;
}