Quick C

Posted on July 1, 2014
Tags: 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;
printf("address of pointee b aka value of a is %p  \n", a);
printf("value of pointee is b  \n", *(int*)(a));
//address of pointee b aka value of a is 0xbefe3588
//value of pointee b is 5 

printf("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 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;
    printf("hey %d ", plus(a,3));
    return 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

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

5 null pointers, structs

struct ll {
    int var;
    struct* ll next;
}

struct ll newlist;
printf("%d",newlist); // 0
printf("%p",newlistptr); // (nil)

printf("%d",newlist.next); // 1824314
printf("%p",newlistptr.next); // 0x294452u205

struct ll* newlistptr;
printf("%d",newlistptr); // 0
printf("%p",newlistptr); // (nil)

printf("%d",(*newlistptr).next); // segfault
printf("%p",(*newlistptr).next); // segfault

6 Strings

char *str;
str = "name"

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.
printf("%s","hello"); 
//init string literal "hello" at SOME address 
//0xfffeee -> "hello"

printf("%s","hello"); 
//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";
s[1]='Y';
//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";
s[1]='Y';
//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>
dup2(fd1,fd2)
  • 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];
pipe( fds );
char line[100];
f = fork();
if (f == 0) {
  close( fds[0] );
  write( fds[1], "hello!", 7);
}
else {
  close( fds[1] );
  read( fds[0], line, sizeof(line) );
}

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 )

6.5.5 Shared Memory

<sys/shm.h> <sys/ipc.h> <sys/types.h>
A segment of heap memory that can be accessed by multiple processes.
Shared memory is accessed via a key that is known by any process that needs to access it.
Shared memory does not get released when a program exits.`

  • 5 Shared memory operations
  • Create the segment (happens once) - shmget
  • Access the segment (happens once per process) - shmget
  • Attach the segment to a variable (once per process) - shmat
  • Detach the segment from a variable (once per process) - shmdt
  • Remove the segment (happens once) - shmctl
int *data;
int shmd;
shmd = shmget(KEY, sizeof(int), IPC_CREAT | 0640);
printf("shmd: %d\n", shmd);
data = shmat(shmd, 0, 0);
printf("data: %p\n", data);
printf("*data: %d\n", *data);
*data = * data + 10;
printf("*data: %d\n", *data);
shmdt(data); 

Server Design

  • Handshake - ensure a connection between 2 programs
  • 3-way-handshake - verify both client and server can BOTH SEND AND RECIEVE DATA
  • Client send msg to Server. (lets server know client can recieve data)
  • NOTE: At this point Server DOES NOT KNOW if client can send data
  • Server send resp to Client. (Let client know Server can recieve and send data)
  • Client send resp to Server. (lets server know client can recieve and send data)

Setup

  • Server creates a FIFO (Well Known Pipe) and waits for a connection.
  • Client creates a “private” FIFO.

sprintf(buffer, "%d", getpid() );

Handshake

  • Client connects to server and sends the private FIFO name.
  • Client waits for a response from the server.
  • Server receives client’s message and removes the WKP.
  • Server connects to client FIFO, sending an initial acknowledgement message.
  • Client receives server’s message, removes its private FIFO.
  • Client sends response to server.

Operation

  • Server and client send information back and forth.

Reset

  • Client exits, server closes any connections to the client.
  • Server recreates the WKP waits for another client.

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";
    puts( hello);
}
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() { 
    puts(hello);
}
.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;
    printf("%d\n",commonVar);
    // sleep(100);
    int soul = fork();
    if(soul == 0){
        a = a *3;
        *b = *b * 2;
        printf("commonVar: %d\n",commonVar);
        printf("pid: %d\n",getpid());
        printf("a:%d\n", a);
        return 0;
    }
    if(soul != 0){
        a = a +1;
        *b = *b * 4;
        printf("commonVar: %d\n",commonVar);
        printf("pid: %d\n",getpid());
        printf("a:%d\n", a);
        return 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;

    printf("Inside thread; x = %d\n", xi);

    return (void*)(xi + 123);
}

int main(int argc, char** argv) {
    pthread_t th;
    pthread_create(&th, NULL, func, (void*)100);

    void* ret_from_thread;
    int ri;
    pthread_join(th, &ret_from_thread);
    ri = (int)ret_from_thread;

    printf("Outside thread, which returned %d\n", ri);
    return 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
    printf("Writing directly to stdout printed last\n"); 
    char buf[4];
    buf[0] = 'h';
    buf[1] = 'e';
    buf[2] = 'y';
    buf[3] = '\n';
    //write is POSIX, fwrite is for all OS
    write(1,buf,4); //1 means stdout
    write(2,buf,4);
    write(0,buf,4);
    write(3,buf,4);
}

void writeNreadFile(){
    printf("Writing to file\n");
    char *buf = (char*) calloc(100, sizeof(char)); //calloc is just malloc but zero'd out
    //! WRITING TO FILE
    int fhandle;
    fhandle = open("bleh.txt",O_RDWR | O_CREAT);
    printf("%p\n",fhandle); 
    int response = write(fhandle, "hilo\n", strlen("hilo"));
    close(fhandle);

    //! READING TO FILE    
}


void writeNreadFile2(){
    char *buf = (char*) calloc(100, sizeof(char)); //calloc is just malloc but zero'd out

    FILE* fhandle;
    fhandle = fopen("bleh2.txt", "w+");
    printf("%d\n",*fhandle);
    fprintf(fhandle,"this is content");
    fclose(fhandle);

}

void readRandom(){
    int randFile = open("/dev/random", O_RDONLY);
    unsigned char s;
    read(randFile, &s, sizeof(char));
    printf("%d\n",s);
    close(randFile);

}

//!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;

    printf("Inside thread; x = %d\n", xi);
    sleep(1);
    return (void*)(xi + 1233);
}


void* func(void* x) {
    int xi = (int)x;

    printf("Inside thread; x = %d\n", xi);

    return (void*)(xi + 123);
}

int main(int argc, char** argv) {
    pthread_t th;
    pthread_t th2;
    pthread_create(&th, NULL, func, (void*)100);
    pthread_create(&th2, NULL, func2, (void*)100);
    void* ret_from_thread;
    int ri;
    pthread_join(th, &ret_from_thread);
    pthread_join(th2, &ret_from_thread);
    ri = (int)ret_from_thread;

    printf("Outside thread, which returned %d\n", ri);
    return 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 *dir = opendir(basePath);

    if (!dir) {
        perror("Error opening directory");
        exit(EXIT_FAILURE);
    }

    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_name[0] == '.')
            continue; // Skip "." and ".."

        snprintf(path, sizeof(path), "%s/%s", basePath, entry->d_name);

        if (entry->d_type == DT_DIR) {
            // It's a directory, recursively list its contents
            listFiles(path, entry->d_name);
        } else {
            // It's a file, print its relative path
            printf("%s/%s\n", relativePath, entry->d_name);
        }
    }

    closedir(dir);
}

int main(int argc, char**argv) {
    const char *directoryPath = argv[1];
    
    // Call the function to list files
    listFiles(directoryPath,"");
    // for(int i = 0; i < n; i++){
    //     printf("%s\n",stack[i]);
    // }
    return 0;
}