More to come...
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
12KB

  1. #ifndef CHAPTER_3_HEADER
  2. #define CHAPTER_3_HEADER
  3. #include "chapter_0.h"
  4. #include "chapter_1.h"
  5. /*
  6. In this chapter, you'll learn about:
  7. - Variadic argument functions
  8. - Basic 'printf' functionality
  9. - More on function pointers
  10. - Control flow switching
  11. - Escape characters
  12. - More on function definitions
  13. 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
  14. 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
  15. 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
  16. 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.
  17. 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
  18. 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
  19. 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
  20. 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.
  21. @C
  22. extern int printf ( const char * format, ...);
  23. extern int sprintf (char * str, const char * format, ...);
  24. extern int snprintf (char * str, size_t size, const char * format, ...);
  25. extern int fprintf (FILE * stream, const char * format, ...);
  26. extern int dprintf (int fd, const char * format, ...);
  27. extern int vprintf ( const char * format, va_list ap);
  28. extern int vsprintf (char * str, const char * format, va_list ap);
  29. extern int vsnprintf (char * str, size_t size, const char * format, va_list ap);
  30. extern int vfprintf (FILE * stream, const char * format, va_list ap);
  31. extern int vdprintf (int fd, const char * format, va_list ap);
  32. @
  33. 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
  34. 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
  35. 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
  36. 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!
  37. 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
  38. 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
  39. 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
  40. 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.
  41. I have very strict rules about how I write C, but I believe that K&R C was very good, with few minor adjustments...
  42. @C
  43. // 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.
  44. int a = 1; printf ("Integer: %d\n", a); // %d - Format signed integer 'int' as decimal, same as '%i', if unsigned then '%u'.
  45. char b = 'A'; printf ("Character: %c\n", b); // %c - Format character (which 'int' implicitly).
  46. char * c = "Heyo"; printf ("%s world!\n", c); // %s - Format string (it must be null terminated).
  47. float d = 1.0F; printf ("%f\n", (double) d); // %f - Format float, but we're converting it to 'double' type.
  48. double e = 1.0; printf ("%F\n", e); // %F - Format double, but same as above, you can specify precision in both of them.
  49. uint32_t f = 0X0011AAU; printf ("0X%X\n", f); // %X - Format signed integer as hexadecimal, 'x' for lowercase and 'X' for uppercase.
  50. size_t g = sizeof (a); printf ("%lu\n", g); // %lu - Format 'size_t' or 'unsigned long int' on any hardware / OS that matters.
  51. int * h = & a; printf ("\t%p", (void *) h); // %p - Format pointer or memory address.
  52. printf ("\nCyaa %% world!\n" ); // %% - Output plain old '%' character.
  53. // You can have more than one arguments, like this:
  54. printf ("%s world!\n Our integer is %d.\n Our character is '%c'.\nCyaa world!\n", c, a, b);
  55. // Never write this:
  56. char * message = "Heyo world!";
  57. printf (message);
  58. // Instead write:
  59. char * message = "Heyo world!";
  60. printf ("%s", message);
  61. // Or just:
  62. printf ("Heyo world!");
  63. @
  64. 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.
  65. 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:
  66. @C
  67. printf ("[%10i]", 144000); // "[ 144000]"
  68. printf ("[%3.6f]", 3.14159235359); // "[ 3.141592]"
  69. @
  70. I don't really use it that much, refer to manual pages for more information with '$ man 3 printf'.
  71. 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
  72. 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
  73. '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
  74. 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.
  75. 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
  76. '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
  77. 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
  78. of reading stuff, try to implement them yourself, it's the best approach to learning anything.
  79. 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
  80. 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.
  81. 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
  82. what a naive function would look like in flat assembly (fasm):
  83. 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
  84. useless example, which is something I avoid in this "book", but it's too early to learn assembly now, just a small show-case.
  85. @C
  86. void increment_by_4 (int * x) {
  87. * x += 4;
  88. }
  89. @
  90. 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
  91. assembly programs, and I keep them clean, not linking it with anything compiled using GCC or other compilers.
  92. @Flat
  93. increment_by_4:
  94. add [global_argument_0], 4
  95. ret
  96. @
  97. 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
  98. use because of how complex the software became, but on that note, it's a good thing. Naturally, I aligned that output a bit.
  99. @Text
  100. 0000000000000000 <increment_by_4>:
  101. 0: 55 | push rbp
  102. 1: 48 89 e5 | mov rbp, rsp
  103. 4: 48 89 7d f8 | mov QWORD PTR [rbp-0x8], rdi
  104. 8: 48 8b 7d f8 | mov rdi, QWORD PTR [rbp-0x8]
  105. C: 8b 07 | mov eax, DWORD PTR [rdi]
  106. E: 83 c0 04 | add eax, 0x4
  107. 11: 89 07 | mov DWORD PTR [rdi], eax
  108. 13: 5d | pop rbp
  109. 14: c3 | ret
  110. @
  111. 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
  112. 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
  113. 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
  114. 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.
  115. 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
  116. 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
  117. 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,
  118. structure or union is visible to them, including other functions. You'll understand them better after few compiler warnings...
  119. When you start writing your own little C programs, I advise you to compile and test it with:
  120. @Shell
  121. # Easy mode:
  122. gcc -Wall -Wpedantic -std=c99 -o my_c_program my_c_source_code.c
  123. clang -Wall -Wpedantic -std=c99 -o my_c_program my_c_source_code.c
  124. valgrind my_c_program
  125. # Hard mode:
  126. gcc -Wall -Wextra -Wpedantic -Werror -ansi -o my_c_program my_c_source_code.c
  127. clang -Weverything -Werror -ansi -o my_c_program my_c_source_code.c
  128. splint -weak my_c_source_code.c
  129. valgrind --show-leak-kinds=all --leak-check=full my_c_program
  130. @
  131. Then again, even if everything compiles nicely, maybe you made a mistake in your algorithm. How to solve that? Well, the answer is same as to question "How to live forever?", it's
  132. "Just don't die."... Know what you're doing, and when you're doing it, and you'll be good to go, otherwise, use "printf" to debug it.
  133. */
  134. extern void print ( char * format, ...); // Notice the "...", which means it can accept any amount of arguments after that 'format'.
  135. 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.
  136. extern void string_print (char * string, char * format, ...); // Same as above again, but it'll copy it to some string.
  137. #endif