This commit is contained in:
Miloslav Ciz 2022-04-09 20:51:52 +02:00
parent 2a3b06eb67
commit 3abdc93103
17 changed files with 160 additions and 24 deletions

View file

@ -530,7 +530,7 @@ x--; // same as: x = x - 1;
// etc.
```
The last two constructs are called **[incrementing](increment.md)** and **[decrementing](decrement.md)**. This just means adding/substracting 1.
The last two constructs are called **[incrementing](increment.md)** and **[decrementing](decrement.md)**. This just means adding/subtracting 1.
In C there is a pretty unique operator called the **[ternary operator](ternary_operator.md)** (ternary for having three [operands](operand.md)). It can be used in expressions just as any other operators such as `+` or `-`. Its format is:
@ -1157,6 +1157,18 @@ We see the `#if` directive has to have a corresponding `#endif` directive that t
#endif
```
Let us talk about one more thing that doesn't fall under the preprocessor language but is related to constants: **enumerations**. Enumeration is a data type that can have values that we specify individually, for example:
```
typedef enum
{
APPLE,
PEAR,
TOMATO
} Fruit;
```
This creates a new data type `Fruit`. Variables of this type may have values `APPLE`, `PEAR` or `TOMATO`, so we may for example do `Fruit myFruit = APPLE;`. These values are in fact integers and the names we give them are just nicknames, so here `APPLE` is equal to 0, `PEAR` to 1 and `TOMATO` to 2.
## Pointers
@ -1337,7 +1349,7 @@ Now let's talk about arrays -- these are a bit special. The important thing is t
Arrays and pointer are kind of a duality -- we can also use array indexing with pointers. For example if we have a pointer declared as `int *x;`, we can access the value `x` points to with a dereference (`*x`), but ALSO with indexing like this: `x[0]`. Accessing index 0 simply means: take the value stored in the variable and add 0 to it, then dereference it. So it achieves the same thing. We can also use higher indices (e.g. `x[10]`), BUT ONLY if `x` actually points to a memory that has at least 11 allocated places.
This leads to a concept called **[pointer arithmetic](pointer_arithmetic.md)**. Pointer arithmetic simply means we can add or substract numbers to pointer values. If we continue with the same pointer as above (`int *x;`), we can actually add numbers to it like `*(x + 1) = 10;`. What does this mean?! It means exactly the same thing as `x[1]`. Adding a number to a pointer shifts that pointer given number of *places* forward. We use the word *places* because each data type takes a different space in memory, for example `char` takes one byte of memory while `int` takes usually 4 (but not always), so shifting a pointer by *N* places means adding *N* times the size of the pointed to data type to the address stored in the pointer.
This leads to a concept called **[pointer arithmetic](pointer_arithmetic.md)**. Pointer arithmetic simply means we can add or subtract numbers to pointer values. If we continue with the same pointer as above (`int *x;`), we can actually add numbers to it like `*(x + 1) = 10;`. What does this mean?! It means exactly the same thing as `x[1]`. Adding a number to a pointer shifts that pointer given number of *places* forward. We use the word *places* because each data type takes a different space in memory, for example `char` takes one byte of memory while `int` takes usually 4 (but not always), so shifting a pointer by *N* places means adding *N* times the size of the pointed to data type to the address stored in the pointer.
This may be a lot information to digest. Let's provide an example to show all this in practice:
@ -1640,6 +1652,32 @@ The line starting with `char *inputChars = malloc(...` creates a pointer to `cha
## Debugging, Optimization
[Debugging](debugging.md) means localizing and fixing [bugs](bug.md) (errors) in your program. In practice there are always bugs, even in very short programs (you've probably already figured that out yourself), some small and insignificant and some pretty bad ones that make your program unusable or vulnerable.
There are two kinds of bugs: **[syntactic](syntax.md) errors** and **[semantic](semantics.md) errors**. A syntactic error is when you write something not obeying the C grammar, it's like a typo or grammatical error in a normal language -- these errors are very easy to detect and fix, a compiler won't be able to understand your program and will point you to the exact place where the error occurs. A semantic error can be much worse -- it's a logical error in the program; the program will compile and run but the program will behave differently than intended. The program may crash, leak memory, give wrong results, run slowly, corrupt files etc. These errors may be hard to spot and fix, especially when they happen in rare situations. We'll be only considering semantic errors from now on.
If we spot a bug, how do we fix it? The first thing is to find a way to **replicate** it, i.e. find the exact steps we need to make with the program to make the bug appear (e.g. "in the menu press keys A and B simultaneously", ...). Next we need to trace and locate which exact line or piece of code is causing the bug. This can either be done with the help of specialized [debuggers](debugger.md) such as [gdb](gdb.md) or [valgrind](valgrind.md), but there's usually a much easier way of using printing functions such as `printf`. (Still do check out the above mentioned debuggers, they're very helpful.)
Let's say your program crashes and you don't know at which line. You simply put prints such as `printf("A\n");` and `printf("B\n);` at the beginning and end of a code you suspect might be causing the crash. Then you run the program: if `A` is printed but `B` isn't, you know the crash happened somewhere between the two prints, so you shift the `B` print a little bit up and so on until you find exactly after which line `B` stops printing -- this is the line that crashes the program. IMPORTANT NOTE: the prints have to have newline (`\n`) at the end, otherwise this method may not work because of output buffering.
Of course, you may use the prints in other ways, for example to detect at which place a value of variable changes to a wrong value. ([Asserts](assert.md) are also good for keeping an eye on correct values of variables.)
What if the program isn't exactly crashing but is giving wrong results? Then you need to trace the program step by step (not exactly line by line, but maybe function by function) and check which step has a problem in it. If for example your game AI is behaving stupid, you firstly check (with prints) if it correctly detects its circumstances, then you check whether it makes the correct decision based on the circumstances, then you check whether the pathfinding algorithm finds the correct path etc. At each step you need to know what the correct behavior should be and you try to find where the behavior is broken.
Knowing how to fix a bug isn't everything, we also need to find the bugs in the first place. **[Testing](testing.md)** is the process of trying to find bugs by simply running and using the program. Remember, testing can't prove there are no bugs in the program, it can only prove bugs exits. You can do testing manually or automate the tests. Automated tests are very important for preventing so called **[regressions](regression.md)** (so the tests are called regression tests). Regression happens when during further development you break some of its already working features (it is very common, don't think it won't be happening to you). Regression test (which can simply be just a normal C program) simply automatically checks whether the already implemented functions still give the same results as before (e.g. if *sin(0) = 0* etc.). These tests should be run and pass before releasing any new version of the program (or even before any commit of new code).
[Optimization](optimization.md) is also a process of improving an already working program, but here we try to make the program more efficient -- the most common goal is to make the program faster, smaller or consume less [RAM](ram.md). This can be a very complex task, so we'll only mention it briefly.
The very basic thing we can do is to turn on automatic optimization with a compiler flag: `-O3` for speed, `-Os` for program size (`-O2` and `-O1` are less aggressive speed optimizations). Yes, it's that simple, you simply add `-O3` and your program gets magically faster. Remember that **optimizations against different resources are often antagonistic**, i.e. speeding up your program typically makes it consume more memory and vice versa. You need to choose. Optimizing manually is a great art. Let's suppose you are optimizing for speed -- the first, most important thing is to locate the part of code that's slowing down you program the most, so called **[bottleneck](bottleneck.md)**. That is the code you want to make faster. Trying to optimize non-bottlenecks doesn't speed up your program as a whole much; imagine you optimize a part of the code that takes 1% of total execution time by 200% -- your program will only get 0.5% faster. Bottlenecks can be found using [profiling](profiling.md) -- measuring the execution time of different parts of the program (e.g. each function). This can be done manually or with tools such a [gprof](gprof.md). Once you know where to optimize, you try to apply different techniques: using algorithms with better [time complexity](time_complexity.md), using [look up tables](lut.md), optimizing [cache](cache.md) behavior and so on. This is beyond the scope of this tutorial.
## Final Program
## Advanced Stuff and Where to Continue
TODO
## Where to Go Next
We haven't covered the whole of C, not even close, but you should have pretty solid basics now. Now you just have to go and write a lot of C programs, that's the only way to truly master C. WARNING: Do not start with an ambitious project such as a 3D game. You won't make it and you'll get demotivated. Start very simple (a Tetris clone perhaps?).
You should definitely learn about common [data structures](data_strucutre.md) ([linked lists](linked_list.md), [binary trees](binary_tree.md), [hash tables](hash.md), ...) and [algorithms](algorithm.md) ([sorting](sorting.md), [searching](search.md), ...). Also take a look at basic [licensing](license.md). Another thing to learn is some [version control system](vcs.md), preferably [git](git.md), because this is how we manage bigger programs and how we collaborate on them. To start making graphical programs you should get familiar with some library such as [SDL](sdl.md).
A great amount of experience can be gained by contributing to some existing project, collaboration really boosts your skill and knowledge of the language. This should only be done when you're at least intermediate. Firstly look up a nice project on some git hosting site, then take a look at the bug tracker and pick a bug or feature that's easy to fix or implement (low hanging fruit).