2023-11-07 10:14:07 -05:00
/*
Copyright ( c ) 2023 : Ognjen ' xolatile ' Milan Robovic
Xhartae is free software ! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation .
And when you do redistribute it or modify it , it will use either version 3 of the License , or ( at yours truly opinion ) any later version .
It is distributed in the hope that it will be useful or harmful , it really depends . . . But no warranty what so ever , seriously . See GNU / GPLv3 .
*/
2023-11-09 09:41:53 -05:00
# ifndef CHAPTER_0_SOURCE // These two, and "#endif" at the end of the file are header guards, we'll talk about them more when the time comes!
2023-11-07 10:14:07 -05:00
# define CHAPTER_0_SOURCE
2023-11-09 09:41:53 -05:00
# include "chapter_0.h" // We're pasting macros, enumerations and function declarations from header file "chapter_0.h" into this file, at this location.
2023-11-07 10:14:07 -05:00
2023-11-07 10:55:27 -05:00
/*
Function ' in ' will perform read system call , literally reading ' size ' bytes from standard input , which is terminal in most kernels unless it ' s redirected to some other file
descriptor , and store it at ' data ' memory address , if there ' s enough space in it . Since this is one of the core functions , if it fails , we want to abort the program and see what
we did wrong . . . Maybe there wasn ' t enough space in ' data ' , maybe ' size ' was negative and it overflowed because read system call internally uses ' size_t / unsigned long int ' , which
is 64 bits wide , maybe we made some other mistake ? Just abort , find the error and fix it .
*/
2023-11-07 10:14:07 -05:00
void in ( void * data , int size ) {
2023-11-09 09:41:53 -05:00
fatal_failure ( data = = NULL , " in: Failed to read from standard input, data is null pointer. " ) ; // This function is defined below, but we can call it here.
fatal_failure ( size = = 0 , " in: Failed to read from standard input, size is zero. " ) ; // That's because we declared it previously. Look at 'out' function.
2023-11-07 10:14:07 -05:00
2023-11-07 10:55:27 -05:00
( void ) read ( STDIN_FILENO , data , ( unsigned long int ) size ) ; // I cast to void type return value of read and write, because I don't really care about it.
2023-11-07 10:14:07 -05:00
}
2023-11-07 10:55:27 -05:00
/*
Similar to ' in ' function and read system call , write system call will store ' size ' bytes from ' data ' memory address into standard output , which is usually what you see in your
terminal . Now , I won ' t talk much about teletypes , virtual terminals , file descriptor redirections and similar stuff , because I ' m not very knowledgable about them . What matters is ,
you have a working operating system , terminal and compiler , and you can make things happen . Once you learn C better than me , and start writing your own multi - threaded kernel , core
2023-11-09 09:41:53 -05:00
libraries , compilers and what not , you ' ll care about those things . I ' ll briefly talk about function structure for ' reallocate ' function soon .
2023-11-07 10:55:27 -05:00
*/
2023-11-07 10:14:07 -05:00
void out ( void * data , int size ) {
2023-11-07 10:55:27 -05:00
fatal_failure ( data = = NULL , " out: Failed to write to standard output, data is null pointer. " ) ; // Notice how we can use function 'fatal_failure' before its' definition.
fatal_failure ( size = = 0 , " out: Failed to write to standard output, size is zero. " ) ; // That's because we declared it in 'chapter_0.h' header file.
2023-11-07 10:14:07 -05:00
( void ) write ( STDOUT_FILENO , data , ( unsigned long int ) size ) ;
}
2023-11-07 10:55:27 -05:00
/*
Function ' echo ' is just a simplification of function ' out ' , because we ' ll be using it a lot , notice that it must accept null terminated strings , which are sort of C - style thing . I
really like them , because you don ' t need to always know size of the string in order to iterate it , but it requires some mental overhead in order to use them without creating hard
to find bugs , which is why newer programming languages consider them unsafe . They ' re not unsafe , they need to be used like intended .
*/
2023-11-07 10:14:07 -05:00
void echo ( char * string ) {
2023-11-07 10:55:27 -05:00
out ( string , string_length ( string ) ) ; // This function fails when we pass a string that's not null terminated, and we don't care to check for errors...
2023-11-07 10:14:07 -05:00
}
2023-11-07 10:55:27 -05:00
void fatal_failure ( int condition , char * message ) { // We use this function to abort the program if condition is met and to print the message.
2023-11-09 16:35:24 -05:00
if ( condition = = TRUE ) { // If the variable 'condition' is not equal to 0, we execute the code in curly braces.
2023-11-07 10:55:27 -05:00
echo ( " [ \033 [1;31mExiting \033 [0m] " ) ; // Simply printing the message using our 'echo' function, but we also use some colours, more on that later.
2023-11-09 09:41:53 -05:00
echo ( message ) ; // Also, notice how "this or that" is implicity 'char *' type... Maybe it's too early to explain it at this point.
echo ( " \n " ) ; // This will only print a new line, we'll see how to use it later.
2023-11-07 10:55:27 -05:00
exit ( EXIT_FAILURE ) ; // This is the function (and '_exit' system call) that aborts the program with a return code.
} // If condition isn't met, function will just return, and nothing is printed, execution continues.
2023-11-07 10:14:07 -05:00
}
2023-11-07 10:55:27 -05:00
void limit ( int * value , int minimum , int maximum ) { // This is somewhat similar to limiting a variable to some values, inclusively, we'll use it later.
2023-11-09 09:41:53 -05:00
if ( value = = NULL ) { return ; } // We shouldn't dereference null pointer, but also don't want to abort the program for small mistake.
2023-11-07 10:55:27 -05:00
if ( * value < = minimum ) { * value = minimum ; } // You can also align similar consecutive statements like this, we'll see it more often in switch statement later on...
2023-11-09 09:41:53 -05:00
if ( * value > = maximum ) { * value = maximum ; } // If we pass a null pointer to this function, it won't do anything, just return.
2023-11-07 10:14:07 -05:00
}
2023-11-07 10:55:27 -05:00
/*
Memory management is a whole new topic that ' s too complex to cover it now in details , and it ' s the source of most security vunrabilities and hidden bugs . For now , just remember
that every program can read , write or execute only parts of the memory that the kernel allows it . Program can request new memory or release old memory , so some other programs can
use it . We ' ll learn to use program called Valgrind to find and fix memory related bugs in later chapters . We rely on functions ' calloc ' , ' realloc ' and ' free ' from < stdlib . h >
header file , and we ' ll avoid ' malloc ' , and use ' realloc ' carefully , because they leave new memory uninitialized .
2023-11-07 16:40:23 -05:00
We ' re internally using function ' calloc ' to request new memory , function ' realloc ' to enlarge existing memory and function ' free ' to release old memory , data that won ' t be used
later in the program . It ' s important to " free " all " malloc/calloc/realloc " - ed memory when program finishes successfully , and in some special cases , even when program fails and
aborts . It ' s important for safety to do so , think of it like open and close braces , if you have some allocations , you should deallocate them later .
Some examples of using them directly ( not wrapping them like I like to do ) are :
2023-11-07 10:55:27 -05:00
2023-11-09 09:41:53 -05:00
@ C
2023-11-07 16:40:23 -05:00
char * data = NULL ;
2023-11-09 15:43:14 -05:00
data = malloc ( 20 * sizeof ( * data ) ) ; // Allocates 20 bytes of memory for 'data'.
data = calloc ( 20 , sizeof ( * data ) ) ; // Allocates 20 bytes also, but initializes them to 0 value.
2023-11-07 16:40:23 -05:00
data = realloc ( data , 20 * sizeof ( * data ) ) ; // When 'data' is null pointer, it will be same as 'malloc', else it will reallocate more memory (for correct usage).
2023-11-09 15:43:14 -05:00
// Also, it's best to just use 'calloc', but it complicates some other tasks.
2023-11-07 16:40:23 -05:00
free ( data ) ; // Deallocates memory, we'll talk about "double free" later.
2023-11-09 09:41:53 -05:00
@
*/
2023-11-07 16:40:23 -05:00
2023-11-07 10:14:07 -05:00
void * allocate ( int size ) {
char * data = NULL ;
data = calloc ( ( unsigned long int ) size , sizeof ( * data ) ) ;
fatal_failure ( data = = NULL , " standard : allocate : Failed to allocate memory, internal function 'calloc' returned null pointer. " ) ;
return ( ( void * ) data ) ;
}
2023-11-09 09:41:53 -05:00
/*
Now , lets see that code formatting in action , with briefly describing function structure in C programming language . Our function is called " reallocate " , its ' inputs ( arguments )
are " data " with type ' void * ' ( pointer to any type of memory address ) and " size " with type ' int ' ( integer ) , and its ' output is also ' void * ' ( some memory address ) . All code
between first ' { ' and last connected ' } ' is part of that function . We ' re using function ' realloc ' inside , but we check for error ( it return ' NULL ' on error ) , then we print message
and abort the program if there was an error , it there wasn ' t , we return new enlarged chunk of memory , changing the " data " variable .
*/
2023-11-07 10:14:07 -05:00
void * reallocate ( void * data , int size ) {
data = realloc ( data , ( unsigned long int ) size ) ;
fatal_failure ( data = = NULL , " standard : reallocate: Failed to reallocate memory, internal function 'realloc' returned null pointer. " ) ;
return ( data ) ;
}
void * deallocate ( void * data ) {
if ( data ! = NULL ) {
free ( data ) ;
}
return ( NULL ) ;
}
2023-11-09 15:43:14 -05:00
/*
This program is intended to be a book - like guide for this source code , which is also a book . We ' ll deal with strings a lot , and they ' re a good example of code formatting which is
the main topic of chapter zero . In function ' string_length ' we have for loop without a body , some people prefer to put ' { } ' or ' ; ' in same or next line , to express the intention
that the loop shouldn ' t have a body ( code block { } ) . I just put ' ; ' on the same line . Also , functions ' string_ * ' could depend on functions ' string_ * _limit ' , but we won ' t do that
now , and since we ' ve already declared them in header file " chapter_0.h " we can define them and call them in whatever order we want . Nice .
@ C
// Simple example of how we could make it dependable on 'string_*_limit' function...
int string_compare ( char * string_0 , char * string_1 ) {
return ( string_0 , string_1 , string_length ( string_0 ) ) ;
}
@
*/
2023-11-09 09:41:53 -05:00
int string_length ( char * string ) {
int length ;
2023-11-09 15:43:14 -05:00
fatal_failure ( string = = NULL , " string_length: String is null pointer. " ) ;
2023-11-09 09:41:53 -05:00
2023-11-09 15:43:14 -05:00
for ( length = 0 ; string [ length ] ! = CHARACTER_NULL ; + + length ) ; // Since in C, strings are null terminated, looping until we see null character is strings' length.
2023-11-09 09:41:53 -05:00
return ( length ) ;
}
2023-11-09 15:43:14 -05:00
/*
Now , I ' ve implemented " unlimited " versions of string comparison , copying and concatenation different from " limited " versions . They correspond with standard library functions
' strcmp ' , ' strcpy ' , ' strcat ' , ' strncmp ' , ' strncpy ' and ' strncat ' found in header file < string . h > . In " unlimited " versions , I rely on the fact that we want to apply the operation
on entire strings , that those strings are null terminated and I used that in my advantage . For example , function ' string_compare ' could be something like this :
@ C
int string_compare ( char * string_0 , char * string_1 ) {
int offset ;
fatal_failure ( string_0 = = NULL , " string_compare: Destination string is null pointer. " ) ;
fatal_failure ( string_1 = = NULL , " string_compare: Source string is null pointer. " ) ;
for ( offset = 0 ; ( string_0 [ offset ] ! = CHARACTER_NULL ) & & ( string_1 [ offset ] ! = CHARACTER_NULL ) ; + + offset ) {
if ( string_0 [ offset ] ! = string_1 [ offset ] ) {
return ( FALSE ) ;
}
}
return ( TRUE ) ;
}
@
2023-11-09 16:35:24 -05:00
And I used this approach below to show that you can solve the problem using different solutions . . . You ' ll notice that " limited " versions have variable ' offset ' of type integer . We
use it to interate the strings , while in " unlimited " versions , we iterate on pointers to those strings , which are pushed to the stack . Both versions work , both versions give the
same results , you can use any of them .
2023-11-09 15:43:14 -05:00
*/
2023-11-09 13:22:59 -05:00
int string_compare ( char * string_0 , char * string_1 ) {
2023-11-09 16:35:24 -05:00
fatal_failure ( string_0 = = NULL , " string_compare: Destination string is null pointer. " ) ; // This will be seen in next 5 functions too, we don't want NULL here.
2023-11-09 15:43:14 -05:00
fatal_failure ( string_1 = = NULL , " string_compare: Source string is null pointer. " ) ;
2023-11-09 16:35:24 -05:00
for ( ; ( * string_0 ! = CHARACTER_NULL ) & & ( * string_1 ! = CHARACTER_NULL ) ; + + string_0 , + + string_1 ) { // We iterate until either string reaches the null character.
if ( * string_0 ! = * string_1 ) { // In case that characters at the same offset are different:
return ( FALSE ) ; // > We return FALSE, 0, since strings aren't the same...
2023-11-09 15:43:14 -05:00
}
2023-11-09 13:22:59 -05:00
}
2023-11-09 15:43:14 -05:00
2023-11-09 16:35:24 -05:00
return ( TRUE ) ; // Otherwise, strings are same, we return TRUE, 1.
2023-11-09 13:22:59 -05:00
}
char * string_copy ( char * string_0 , char * string_1 ) {
fatal_failure ( string_0 = = NULL , " string_copy: Destination string is null pointer. " ) ;
fatal_failure ( string_1 = = NULL , " string_copy: Source string is null pointer. " ) ;
2023-11-09 16:35:24 -05:00
for ( ; * string_1 ! = CHARACTER_NULL ; + + string_0 , + + string_1 ) { // This time and in next function, we iterate only source string.
* string_0 = * string_1 ; // And we assign character at the same offset to destination string (aka copy it).
2023-11-09 13:22:59 -05:00
}
2023-11-09 16:35:24 -05:00
* string_0 = * string_1 ; // Copying null termination, since the loop stopped on that condition.
2023-11-09 15:43:14 -05:00
2023-11-09 16:35:24 -05:00
return ( string_0 ) ; // Lastly, we return the destination string, in order to be able to bind functions.
2023-11-09 13:22:59 -05:00
}
char * string_concatenate ( char * string_0 , char * string_1 ) {
fatal_failure ( string_0 = = NULL , " string_concatenate: Destination string is null pointer. " ) ;
fatal_failure ( string_1 = = NULL , " string_concatenate: Source string is null pointer. " ) ;
2023-11-09 16:35:24 -05:00
string_0 + = string_length ( string_0 ) ; // We'll first offset destination string to the end of it.
// Because we want to start copying from the end, aka concatenate it.
for ( ; * string_1 ! = CHARACTER_NULL ; + + string_0 , + + string_1 ) { // The rest of the function is same as string_copy, so:
* string_0 = * string_1 ; // We could even use it here, but that defies the purpose of learning now.
2023-11-09 13:22:59 -05:00
}
2023-11-09 16:35:24 -05:00
* string_0 = CHARACTER_NULL ; // Again, assign null termination.
2023-11-09 13:22:59 -05:00
return ( string_0 ) ;
}
2023-11-09 16:35:24 -05:00
/*
As for " limited " versions of previous 3 functions , they do the same thing , but are capped to some variable ' limit ' . These functions have their own use - case , for example , if
strings aren ' t null terminated , if you ' re not sure that they are null terminated , if we ' re dealing with binary ( not textual ) data ( casted to char * ) , and many more cases .
*/
2023-11-09 13:22:59 -05:00
int string_compare_limit ( char * string_0 , char * string_1 , int limit ) {
2023-11-09 15:43:14 -05:00
int offset ;
2023-11-09 13:22:59 -05:00
fatal_failure ( string_0 = = NULL , " string_compare_limit: Destination string is null pointer. " ) ;
fatal_failure ( string_1 = = NULL , " string_compare_limit: Source string is null pointer. " ) ;
2023-11-09 16:35:24 -05:00
for ( offset = 0 ; offset < limit ; + + offset ) {
2023-11-09 15:43:14 -05:00
if ( string_0 [ offset ] ! = string_1 [ offset ] ) {
return ( FALSE ) ;
2023-11-09 13:22:59 -05:00
}
}
2023-11-09 15:43:14 -05:00
return ( TRUE ) ;
2023-11-09 13:22:59 -05:00
}
char * string_copy_limit ( char * string_0 , char * string_1 , int limit ) {
2023-11-09 15:43:14 -05:00
int offset ;
2023-11-09 13:22:59 -05:00
fatal_failure ( string_0 = = NULL , " string_copy_limit: Destination string is null pointer. " ) ;
2023-11-09 16:35:24 -05:00
fatal_failure ( string_1 = = NULL , " string_copy_limit: Source string is null pointer. " ) ;
2023-11-09 13:22:59 -05:00
2023-11-09 15:43:14 -05:00
if ( ( limit < = 0 ) | | ( string_1 = = NULL ) ) {
2023-11-09 13:22:59 -05:00
return ( string_0 ) ;
}
2023-11-09 16:35:24 -05:00
for ( offset = 0 ; offset < limit ; + + offset ) {
2023-11-09 15:43:14 -05:00
string_0 [ offset ] = string_1 [ offset ] ;
2023-11-09 13:22:59 -05:00
}
return ( string_0 ) ;
}
char * string_concatenate_limit ( char * string_0 , char * string_1 , int limit ) {
2023-11-09 15:43:14 -05:00
int offset , length_0 , length_1 ;
2023-11-09 13:22:59 -05:00
fatal_failure ( string_0 = = NULL , " string_concatenate_limit: Destination string is null pointer. " ) ;
2023-11-09 16:35:24 -05:00
fatal_failure ( string_1 = = NULL , " string_concatenate_limit: Source string is null pointer. " ) ;
2023-11-09 13:22:59 -05:00
2023-11-09 15:43:14 -05:00
if ( ( limit < = 0 ) | | ( string_1 = = NULL ) ) {
2023-11-09 13:22:59 -05:00
return ( string_0 ) ;
}
length_0 = string_length ( string_0 ) ;
length_1 = string_length ( string_1 ) ;
2023-11-09 16:35:24 -05:00
for ( offset = 0 ; ( offset < length_1 ) & & ( offset < limit ) ; + + offset ) {
2023-11-09 15:43:14 -05:00
string_0 [ length_0 + offset ] = string_1 [ offset ] ;
2023-11-09 13:22:59 -05:00
}
return ( string_0 ) ;
}
2023-11-07 10:14:07 -05:00
# endif