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 .
*/
# ifndef CHAPTER_0_SOURCE
# define CHAPTER_0_SOURCE
# include "chapter_0.h"
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 ) {
fatal_failure ( data = = NULL , " in: Failed to read from standard input, data is null pointer. " ) ;
fatal_failure ( size = = 0 , " in: Failed to read from standard input, size is zero. " ) ;
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
libraries , compilers and what not , you ' ll care about those things .
*/
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.
if ( condition ! = 0 ) { // If the variable 'condition' is not equal to 0, we execute the code in curly braces.
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-07 10:14:07 -05:00
echo ( message ) ;
echo ( " \n " ) ;
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.
if ( value = = NULL ) { return ; } // We shouldn't dereference null pointer, but also don't want to abort the program for small mistake.
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...
if ( * value > = maximum ) { * value = maximum ; }
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-07 16:40:23 -05:00
/**
char * data = NULL ;
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.
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).
free ( data ) ; // Deallocates memory, we'll talk about "double free" later.
* */
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 ) ;
}
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 ) ;
}
# endif