ソースを参照

Updates on chapter 4 mostly...

master
コミット
959f717895
4個のファイルの変更101行の追加20行の削除
  1. +2
    -2
      chapter/chapter_2.h
  2. +95
    -14
      chapter/chapter_3.h
  3. +1
    -1
      chapter/chapter_4.c
  4. +3
    -3
      chapter/chapter_4.h

+ 2
- 2
chapter/chapter_2.h ファイルの表示

@@ -10,8 +10,8 @@ In this chapter, you'll learn about:
- Curses library (libncurses "clone")
- More on function declarations
- Escape sequences
- General scope
- Sane program layout
- General scope and design layout
- Function and variable definitions
- Terminal input and output

Lets talk about function declaration. In C, sometimes you need to declare functions that you'll use before they're called. I prefer to always declare functions except when I'm


+ 95
- 14
chapter/chapter_3.h ファイルの表示

@@ -9,10 +9,10 @@ In this chapter, you'll learn about:

- Variadic argument functions
- Basic 'printf' functionality
- Type safety
- More on function pointers
- Control flow switching
- Escape characters
- More on function definitions

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
@@ -44,36 +44,50 @@ something I don't like to do mostly. For example, if you pass a 'float' type var
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
synchronized differently. Because of how they're buffered, mixing them can (in some cases) end up with mixed output.
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...

@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.

int a = 1; printf ("Integer: %d\n", a);
char b = 'A'; printf ("Character: %c\n", b);
char * c = "Heyo"; printf ("%s world!\n", c);
float d = 1.0F; printf ("%f\n", (double) d);
double e = 1.0; printf ("%f\n", e);
uint32_t f = 0X0011AAU; printf ("0X%X\n", f);
size_t g = sizeof (a); printf ("%ul\n", g);
int * h = & a; printf ("\t%p", (void *) h);
printf ("\nCyaa world!\n" );
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 = 0X0011AAU; 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 ("\nCyaa %% world!\n" ); // %% - Output plain old '%' character.

// You can have more than one arguments, like this:

printf ("%s world!\n Our integer is %d.\n Our character is '%c'.\nCyaa world!\n", c, a, b);

// Never do this:
// Never write this:
char * message = "Heyo world!";
printf (message);

// Instead:
// Instead write:
char * message = "Heyo world!";
printf ("%s", message);
// Or:
// Or just:
printf ("Heyo world!");
@

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'.

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
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
@@ -86,6 +100,73 @@ of reading stuff, try to implement them yourself, it's the best approach to lear

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.

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 7d f8 | mov QWORD PTR [rbp-0x8], rdi
8: 48 8b 7d f8 | mov rdi, QWORD PTR [rbp-0x8]
C: 8b 07 | mov eax, DWORD PTR [rdi]
E: 83 c0 04 | add eax, 0x4
11: 89 07 | mov DWORD PTR [rdi], eax
13: 5d | 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
@
*/

extern void print ( char * format, ...); // Notice the "...", which means it can accept any amount of arguments after that 'format'.


+ 1
- 1
chapter/chapter_4.c ファイルの表示

@@ -303,7 +303,7 @@ end hello_world; -- And this is Ada's way of saying '}'.
*/

void syntax_highlight_ada (void) {
char * separators = ".,:;<=>#+*-/&|()'\" \t\r\n";
char * separators = ".,:;<=>#+*-/&|()\" \t\r\n";

char * keywords [] = {
"abort", "else", "new", "return", "abs", "elsif", "not", "reverse",


+ 3
- 3
chapter/chapter_4.h ファイルの表示

@@ -28,9 +28,9 @@ example, some of them will require functions from some or all previous chapter s
chapters. Instead of that, we'll (re)implement newer stuff with different approach if necessary. That way, you can be sure that if you're reading chapter four, for example, it'll
only use functions and variables defined in chapters zero to three. Lets begin.

I'll write this huge 'program_curses_view_file' function in somewhat procedural style of programming, so to say, and in the next chapter, we'll use more modular way, using many more
functions. Learning anything, including the C programming language, is like a journey. Maybe you think it won't last long, and it ends up being quite long journey, or maybe you
think it'll be very long, that you'll walk miles and miles, and it ends up being short (you rage-quit). The final destination you're going towards always depends on where you
I'll write this huge 'program_curses_view_file' function in somewhat procedural style of programming, so to say, and in the next chapter, we'll use more modular way, using many
more functions. Learning anything, including the C programming language, is like a journey. Maybe you think it won't last long, and it ends up being quite long journey, or maybe
you think it'll be very long, that you'll walk miles and miles, and it ends up being short (you rage-quit). The final destination you're going towards always depends on where you
left-off and where you're coming from. For example, if you wrote Ada, you'll like chapter four, if you wrote C++, you'll like chapter five.

I'll also list a few "traps" right here, where most programmers get caught in:


読み込み中…
キャンセル
保存