2023-11-13 21:03:29 -05:00
# ifndef CHAPTER_3_HEADER
# define CHAPTER_3_HEADER
# include "chapter_0.h"
# include "chapter_1.h"
/*
2023-12-03 07:19:51 -05:00
In this chapter , you ' ll learn about :
- Variadic argument functions
- Basic ' printf ' functionality
- More on function pointers
- Control flow switching
- Escape characters
2023-12-04 06:22:34 -05:00
- More on function definitions
2023-12-03 07:19:51 -05:00
2023-11-13 21:03:29 -05:00
Now , time has come to talk about ( sadly ) the most important standard library function in C programming language . Function ' printf ' is like some semi - drunk old man , stumbling on
the streets , talking to himself , talking to other people who try to ignore him , but if you stand and talk to him , he ' ll sometimes start talking about random things , and sometimes
he ' ll stay on - topic . So , what does function ' printf ' do ? Essentially , it ' ll print formatted string to standard output , formatted in this case means that in the main string ( first
argument to ' printf ' function ) there are special characters that ' ll accept variadic arguments , format them ( convert them to string in special manner ) and output that final string .
It ' s located in < stdio . h > header file , and it ' s commonly used through - out most C programs . You can learn more about it from manual pages with command ' man 3 printf ' , and below you
can see some variations of that function . I just realigned them and added ' extern ' , which C assumes as default ( but some compilers would generate a warning for that ) . We ' ll learn
to implement variadic functions as well in this chapter , but I consider that a bad practice , even tho they can be extremely useful . Functions we learned to declare and define so
far have constant and limited amount of arguments , while variadic functions can have any amount of arguments greater than one , as you can see below .
@ C
extern int printf ( const char * format , . . . ) ;
extern int sprintf ( char * str , const char * format , . . . ) ;
extern int snprintf ( char * str , size_t size , const char * format , . . . ) ;
extern int fprintf ( FILE * stream , const char * format , . . . ) ;
extern int dprintf ( int fd , const char * format , . . . ) ;
extern int vprintf ( const char * format , va_list ap ) ;
extern int vsprintf ( char * str , const char * format , va_list ap ) ;
extern int vsnprintf ( char * str , size_t size , const char * format , va_list ap ) ;
extern int vfprintf ( FILE * stream , const char * format , va_list ap ) ;
extern int vdprintf ( int fd , const char * format , va_list ap ) ;
@
Why do I dislike them ? Because they ' re implemented with the help of macros from < stdarg . h > header file , in not particularly good way , and I dislike using ' va_ * ' because I simply
don ' t like the naming style . Also , you can ' t properly verify that you won ' t cause tiny bugs in your program , so before writing ' printf ' function , you need to think , which is
something I don ' t like to do mostly . For example , if you pass a ' float ' type variable to ' printf ' function , with " %f " in its ' format , you need to convert it to ' double ' , because
that ' va_arg ' macro accepts only fully promoted types , which we ' ll explain below . All that aside , they can be very useful in specific cases !
You ' d use it something like these few examples below , and it ' s a good practise not to mix ' printf ' with ' write ' functions ( if you ' re using both of them ) , because they ' re
2023-12-04 06:22:34 -05:00
synchronized differently . Because of how they ' re buffered , mixing them can ( in some cases ) end up with mixed output . So , what the hell are fully promoted types ? In short , C really
has 4 types , ' int ' which is default type ( K & R C , but damn compilers warn about that sadly ) , ' double ' for IEEE754 uglyness , ' char * ' for string literals and ' void * ' for literal
memory locations , which are implicit most of the time , as well as implicit labels . I really meant " in short " , and ignore ' size_t ' , I dislike it .
I have very strict rules about how I write C , but I believe that K & R C was very good , with few minor adjustments . . .
2023-11-13 21:03:29 -05:00
@ C
// And ignore the alignment here, it's just an usage example. You can put all of this into 'main' function in some temporary file, compile it and run it.
2023-12-04 06:22:34 -05:00
int a = 1 ; printf ( " Integer: %d \n " , a ) ; // %d - Format signed integer 'int' as decimal, same as '%i', if unsigned then '%u'.
char b = ' A ' ; printf ( " Character: %c \n " , b ) ; // %c - Format character (which 'int' implicitly).
char * c = " Heyo " ; printf ( " %s world! \n " , c ) ; // %s - Format string (it must be null terminated).
float d = 1.0F ; printf ( " %f \n " , ( double ) d ) ; // %f - Format float, but we're converting it to 'double' type.
double e = 1.0 ; printf ( " %F \n " , e ) ; // %F - Format double, but same as above, you can specify precision in both of them.
uint32_t f = 0 X0011AAU ; printf ( " 0X%X \n " , f ) ; // %X - Format signed integer as hexadecimal, 'x' for lowercase and 'X' for uppercase.
size_t g = sizeof ( a ) ; printf ( " %lu \n " , g ) ; // %lu - Format 'size_t' or 'unsigned long int' on any hardware / OS that matters.
int * h = & a ; printf ( " \t %p " , ( void * ) h ) ; // %p - Format pointer or memory address.
printf ( " \n Cyaa %% world! \n " ) ; // %% - Output plain old '%' character.
2023-11-13 21:03:29 -05:00
// You can have more than one arguments, like this:
printf ( " %s world! \n Our integer is %d. \n Our character is '%c'. \n Cyaa world! \n " , c , a , b ) ;
2023-12-04 06:22:34 -05:00
// Never write this:
2023-11-13 21:03:29 -05:00
char * message = " Heyo world! " ;
printf ( message ) ;
2023-12-04 06:22:34 -05:00
// Instead write:
2023-11-13 21:03:29 -05:00
char * message = " Heyo world! " ;
printf ( " %s " , message ) ;
2023-12-04 06:22:34 -05:00
// Or just:
2023-11-13 21:03:29 -05:00
printf ( " Heyo world! " ) ;
@
2023-12-04 06:22:34 -05:00
So , the nice thing function ' printf ' from < stdio . h > has , and our function ' print ' doesn ' t , is alignment . Also , if you used Fortran programming language , then you ' ll value C more .
You can align data and text to left or right , pad them with a whitespace or zeroes in case of numbers , here ' s how to do it :
@ C
printf ( " [%10i] " , 144000 ) ; // "[ 144000]"
printf ( " [%3.6f] " , 3.14159235359 ) ; // "[ 3.141592]"
@
I don ' t really use it that much , refer to manual pages for more information with ' $ man 3 printf ' .
2023-11-13 21:03:29 -05:00
Now , don ' t get scared , C is old language , and I ' m aware that this looks like someone made a joke 50 years ago and nobody understood it , they took it serious . In the end , variadic
2023-11-26 15:11:45 -05:00
argument list type ' va_list ' is black magic , as well as other ' va_ * ' stuff . Since ' va_arg ' takes only fully promoted types , we ' re left with types ' int ' , ' double ' , ' void * ' and
' char * ' . Also , keep in mind that you don ' t need more arguments in ' printf ' function , only that " const char * fmt " needs to be there always . It may take some time to get used to
this function , but you can use it for very quick and easy debugging . If you ' re not sure what value your variables hold at some point , you can just print it there .
2023-11-13 21:03:29 -05:00
I ' ll show you how to implement variadic argument functions , and we ' ll use these in few places , but I ' m still not a big fan of them . Of course , we won ' t implement everything that
' printf ' function from standard library has , or any more of its ' alternatives , just these three below . However , we ' ll add few small additions , like colouring text with ASCII
escape sequences and doing some basic formatting . Once you learn C better , you should take a look at manual pages for functions that you find interesting . Or even better , instead
of reading stuff , try to implement them yourself , it ' s the best approach to learning anything .
2023-11-26 15:11:45 -05:00
If you wonder why did we cover curses in chapter two , and printing in chapter three , which is simpler and shorter essentially , it ' s because curses use few " black magic " functions
from header file < termios . h > , while printing uses < stdarg . h > , which is more difficult to use . We ' ll use it more in later chapters , but only as a show - case .
2023-12-04 06:22:34 -05:00
You ' ll see a clear use - case of function pointers in chapter three , they simply hold the memory address of some function of specific type . In order to understand it better , here ' s
what a naive function would look like in flat assembly ( fasm ) :
I compiled this with ' $ gcc - c - o temporary . o temporary . c ' , it outputs ELF64 object file , with no memory addresses , since it ' s not linked yet . Of course , this is very trivial and
useless example , which is something I avoid in this " book " , but it ' s too early to learn assembly now , just a small show - case .
@ C
void increment_by_4 ( int * x ) {
* x + = 4 ;
}
@
This is how I ' d do it in flat assembly , without caring for ABI or anything else , pure assembly program , not linked with other object files . I use a lot of global state in my
assembly programs , and I keep them clean , not linking it with anything compiled using GCC or other compilers .
@ Flat
increment_by_4 :
add [ global_argument_0 ] , 4
ret
@
This is how output of ' $ objdump - Mintel - d temporary . o ' looks like , aka how GCC does it , which is ABI - safe . ABI stands for application binary interface , and it ' s something people
use because of how complex the software became , but on that note , it ' s a good thing . Naturally , I aligned that output a bit .
@ Text
0000000000000000 < increment_by_4 > :
0 : 55 | push rbp
1 : 48 89 e5 | mov rbp , rsp
4 : 48 89 7 d f8 | mov QWORD PTR [ rbp - 0x8 ] , rdi
8 : 48 8 b 7 d f8 | mov rdi , QWORD PTR [ rbp - 0x8 ]
C : 8 b 07 | mov eax , DWORD PTR [ rdi ]
E : 83 c0 04 | add eax , 0x4
11 : 89 07 | mov DWORD PTR [ rdi ] , eax
13 : 5 d | pop rbp
14 : c3 | ret
@
So , as you can see , a function is just a memory address of some point in readable and executable part of binary file , our program ( executable ) or object file . It can be executed
with ' call ' instruction , take global variables ( in assembly ) as arguments , or push them and pop them from the stack , it all depends . C programming languages , and most compilers
ensure that when you use function pointers , you know exactly at what kind of function they ' re pointing to , it ' ll generate compiler warnings if it expects a pointer to one type of
function , and gets some other one . If this doesn ' t make any sense to you , maybe you should look into the source code , or even better , write it yourself .
And our last part of " core C " are more details about function definitions . You already saw them in C source files of previous chapters , and there isn ' t much to add , but think of
them as core units of a program . They can ' t be nested , they are quirky from time to time , they can ' t be anonymous like some types , enumerations , structures and unions , which is
really sad in my opinion , I ' d use anonymous functions all the time . Also , functions can only operate on their arguments and their local variables , plus whatever type , enumeration ,
structure or union is visible to them , including other functions . You ' ll understand them better after few compiler warnings . . .
When you start writing your own little C programs , I advise you to compile and test it with :
@ Shell
# Easy mode:
gcc - Wall - Wpedantic - std = c99 - o my_c_program my_c_source_code . c
clang - Wall - Wpedantic - std = c99 - o my_c_program my_c_source_code . c
valgrind my_c_program
# Hard mode:
gcc - Wall - Wextra - Wpedantic - Werror - ansi - o my_c_program my_c_source_code . c
clang - Weverything - Werror - ansi - o my_c_program my_c_source_code . c
splint - weak my_c_source_code . c
valgrind - - show - leak - kinds = all - - leak - check = full my_c_program
@
2023-11-13 21:03:29 -05:00
*/
extern void print ( char * format , . . . ) ; // Notice the "...", which means it can accept any amount of arguments after that 'format'.
extern void file_print ( int file , char * format , . . . ) ; // Same as above, but it won't print to terminal, it'll write it to a file descriptor instead.
extern void string_print ( char * string , char * format , . . . ) ; // Same as above again, but it'll copy it to some string.
# endif