master
Miloslav Ciz 2 years ago
parent 56f93e1f56
commit 24e10f31df

@ -1,5 +1,7 @@
# C
{ We have a [C tutorial](c_tutorial.md)! ~drummyfish }
C is a low-level, statically typed imperative compiled language, the go-to language of most [less retarded software](lrs.md). It is the absolutely preferred language of the [suckless](suckless.md) community as well as of most true experts, for example the [Linux](linux.md) and [OpenBSD](openbsd.md) developers, because of its good minimal design, level of control, uncontested performance and a greatly established and tested status.
C is usually not considered an easy language to learn because of its low level nature: it requires good understanding of how a computer actually works and doesn't prevent the programmer from shooting himself in the foot. Programmer is given full control (and therefore responsibility). There are things considered "tricky" which one must be aware of, such as undefined behavior of certain operators and raw pointers. This is what can discourage a lot of modern "coding monkeys" from choosing C, but it's also what inevitably allows such great performance -- undefined behavior allows the compiler to choose the most efficient implementation.

@ -13,8 +13,8 @@ You should probably know at least the completely basic ideas of programming befo
- Extremely **fast and efficient**.
- Very **widely supported and portable** to almost anything.
- **[Low level](low_level.md)**, i.e. there is relatively little [abstraction](abstraction.md) and not many comfortable built-in functionality such as [garbage collection](garbage_collection.md), you have to write many things yourself, you will deal with [pointers](pointer.md), [endianness](endianness.md) etc.
- [Imperative](imperative.md), without [object oriented programming](oop.md).
- Considered **hard**, but in certain ways it's simple, it lacks [bloat](bloat.md) and [bullshit](bullshit.md) of "[modern](modern.md)" languages which is an essential thing. It will take long to learn but it's the most basic thing you should know if you want to create good software. You won't regret.
- [Imperative](imperative.md) (based on sequences of commands), without [object oriented programming](oop.md).
- Considered **hard**, but in certain ways it's simple, it lacks [bloat](bloat.md) and [bullshit](bullshit.md) of "[modern](modern.md)" languages which is an essential thing. It will take long to learn (don't worry, not nearly as long as learning a foreign language) but it's the most basic thing you should know if you want to create good software. You won't regret.
- **Not holding your hand**, i.e. you may very easily "shoot yourself in your foot" and crash your program. This is the price for the language's power.
- Very old, well established and tested by time.
- Recommended by us for serious programs.
@ -1159,10 +1159,185 @@ We see the `#if` directive has to have a corresponding `#endif` directive that t
Pointers are an advanced topic that many people fear -- many complain they're hard to learn, others complain about memory unsafety and potential dangers of using pointers. These people are stupid, pointers are great.
But beware, there may be too much new information in the first read. Don't get scared, give it some time.
Pointers allow us to do certain advanced things such as allocate dynamic memory, return multiple values from functions, inspect content of memory or use functions in similar ways in which we use variables.
A **[pointer](pointer.md)** is nothing complicated: it is a **data type that can hold a memory address** (plus the information of what data type should be stored at that address). An address is simply a number. Why can't we simply use an `int` for an address? Because the size of `int` and a pointer may differ, the size of pointer depends on each platform's address width. It is also good when the compiler knows a certain variable is supposed to point to a memory (and to which type) -- this can prevent bugs.
It's important to remember that a pointer is not a pure address but it also knows about the data type it is pointing to, so there are many kinds of pointers: a pointer to `int`, a pointer to `char`, a pointer to a specific struct type etc.
A variable of pointer type is created similarly to a normal variable, we just add `*` after the data type, for example `int *x` is a variable named `x` that is a pointer to `int`.
But how do we assign a value to the pointer? To do this, we need an address of something, e.g. of some variable. To get an address of a variable we use the `&` character, i.e. `&a` is the address of a variable `a`.
The last basic thing we need to know is how to **[dereference](dereference.md)** a pointer. Dereferencing means accessing the value at the address that's stored in the pointer, i.e. working with the pointed to value. This is again done (maybe a bit confusingly) with `*` character in front of a pointer, e.g. if `x` is a pointer to `int`, `*x` is the `int` value to which the pointer is pointing. An example can perhaps make it clearer.
```
#include <stdio.h>
int main(void)
{
int normalVariable = 10;
int *pointer;
pointer = &normalVariable;
printf("address in pointer: %p\n",pointer);
printf("value at this address: %d\n",*pointer);
*pointer = *pointer + 10;
printf("normalVariable: %d\n",normalVariable);
return 0;
}
```
This may print e.g.:
```
address in pointer: 0x7fff226fe2ec
value at this address: 10
normalVariable: 20
```
`int *pointer;` creates a pointer to `int` with name `pointer`. Next we make the pointer point to the variable `normalVariable`, i.e. we get the address of the variable with `&normalVariable` and assign it normally to `pointer`. Next we print firstly the address in the pointer (accessed with `pointer`) and the value at this address, for which we use dereference as `*pointer`. At the next line we see that we can also use dereference for writing to the pointed address, i.e. doing `*pointer = *pointer + 10;` is the same as doing `normalVariable = normalVariable + 10;`. The last line shows that the value in `normalVariable` has indeed changed.
IMPORTANT NOTE: **You cannot write to random addresses**! This will crash your program. To be able to write to a certain address it must be *[allocated](allocation.md)*, i.e. reserved for use. Addresses of variables are allocated by the compiler and can be written to.
There's a special value called `NULL` (a macro defined in the standard library) that is meant to be assigned to pointer that point to "nothing". So when we have a pointer `p` that's currently not supposed to point to anything, we do `p = NULL;`. In a safe code we should always check (with `if`) whether a pointer is not `NULL` before dereferencing it, and if it is, then NOT dereference it. This isn't required but is considered a "good practice" in safe code, storing `NULL` in pointers that point nowhere prevents dereferencing random or unallocated addresses which would crash the program.
But what can pointers be good for? Many things, for example we can kind of "store variables in variables", i.e. a pointer is a variable which says which variable we are now using, and we can switch between variable any time. E.g.:
```
#include <stdio.h>
int backAccountMonica = 1000;
int backAccountBob = -550;
int backAccountJose = 700;
int *payingAccount; // pointer to who's currently paying
void payBills(void)
{
*payingAccount -= 200;
}
void buyFood(void)
{
*payingAccount -= 50;
}
void buyGas(void)
{
*payingAccount -= 20;
}
int main(void)
{
// let Jose pay first
payingAccount = &backAccountJose;
payBills();
buyFood();
buyGas();
// that's enough, now let Monica pay
payingAccount = &backAccountMonica;
buyFood();
buyGas();
buyFood();
buyFood();
// now it's Bob's turn
payBills();
buyFood();
buyFood();
buyGas();
printf("Monika has $%d left.\n",backAccountMonica);
printf("Jose has $%d left.\n",backAccountJose);
printf("backAccountBob has $%d left.\n",backAccountBob);
return 0;
}
```
Well, this could be similarly achieved with arrays, but pointers have more uses. For example they allow us to **return multiple values by a function**. Again, remember that we said that (with the exception of arrays) a function cannot modify a variable passed to it because it always makes its own local copy of it? We can bypass this by, instead of giving the function the value of the variable, giving it the address of the variable. The function can read the value of that variable (with dereference) but it can also CHANGE the value, it simply writes a new value to that address (again, using dereference). This example shows it:
```
#include <stdio.h>
#include <math.h>
#define PI 3.141592
// returns 2D coordinates of a point on a unit circle
void getUnitCirclePoint(float angle, float *x, float *y)
{
*x = sin(angle);
*y = cos(angle);
}
int main(void)
{
for (int i = 0; i < 8; ++i)
{
float pointX, pointY;
getUnitCirclePoint(i * 0.125 * 2 * PI,&pointX,&pointY);
printf("%lf %lf\n",pointX,pointY);
}
return 0;
}
```
Function `getUnitCirclePoint` doesn't return any value in the strict sense, but thank to pointers it effectively returns two `float` values via its parameters `x` and `y`. These parameters are of the data type pointer to `int` (as there's `*` in front of them). When we call the function with `getUnitCirclePoint(i * 0.125 * 2 * PI,&pointX,&pointY);`, we hand over the addresses of the variables `pointX` and `pointY` (which belong to the `main` function and couldn't normally be accessed in `getUnitCirclePoint`). The function can then compute values and write them to these addresses (with dereference, `*x` and `*y`), changing the values in `pointX` and `pointY`, effectively returning two values.
Now let's take a look at pointers to structs. Everything basically works the same here, but there's one thing to know about, a [syntactic sugar](sugar.md) known as an arrow (`->`). Example:
```
#include <stdio.h>
typedef struct
{
int a;
int b;
} SomeStruct;
SomeStruct s;
SomeStruct *sPointer;
int main(void)
{
sPointer = &s;
(*sPointer).a = 10; // without arrow
sPointer->b = 20; // same as (*sPointer).b = 20
printf("%d\n",s.a);
printf("%d\n",s.b);
return 0;
}
```
Here we are trying to write values to a struct through pointers. Without using the arrow we can simply dereference the pointer with `*`, put brackets around and access the member of the struct normally. This shows the line `(*sPointer).a = 10;`. Using an arrow achieves the same thing but is perhaps a bit more readable, as seen in the line `sPointer->b = 20;`. The arrow is simply a special shorthand and doesn't need any brackets.
Now let's talk about arrays -- these are a bit special. The important thing is that **an array is itself basically a pointer**. What does this mean? If we create an array, let's say `int myArray[10];`, then `myArray` is basically a pointer to `int` in which the address of the first array item is stored. When we index the array, e.g. like `myArray[3] = 1;`, behind the scenes there is basically a dereference because the index 3 means: 3 places after the address pointed to by `myArray`. So when we index an array, the compiler takes the address stored in `myArray` (the address of the array start) and adds 3 to it (well, kind of) by which it gets the address of the item we want to access, and then dereferences this address.
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 may be a lot information to digest. Let's provide an example to show all this in practice:
TODO
## More on Functions (Recursion, Function Pointers)

Loading…
Cancel
Save