diff --git a/c_tutorial.md b/c_tutorial.md index 5eb53ea..b020b1b 100644 --- a/c_tutorial.md +++ b/c_tutorial.md @@ -1155,6 +1155,7 @@ We see the `#if` directive has to have a corresponding `#endif` directive that t #endif ``` + ## Pointers 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. @@ -1167,7 +1168,7 @@ A **[pointer](pointer.md)** is nothing complicated: it is a **data type that can 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`. +A variable of pointer type is created similarly to a normal variable, we just add `*` after the data type, for example `int *x;` creates a variable named `x` that is a pointer to `int` (some people would write this as `int* x;`). 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`. @@ -1202,11 +1203,11 @@ 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. +`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;` here 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. +IMPORTANT NOTE: **You generally cannot read and write from/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 safely operated with. -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. +There's a special value called `NULL` (a macro defined in the standard library) that is meant to be assigned to pointer that points 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.: @@ -1338,7 +1339,188 @@ This leads to a concept called **[pointer arithmetic](pointer_arithmetic.md)**. This may be a lot information to digest. Let's provide an example to show all this in practice: -TODO +``` +#include + +// our own string print function +void printString(char *s) +{ + int position = 0; + + while (s[position] != 0) + { + putchar(s[position]); + position += 1; + } +} + +// returns the length of string s +int stringLength(char *s) +{ + int length = 0; + + while (*s != 0) // count until terminating 0 + { + length += 1; + s += 1; // shift the pointer one character to right + } + + return length; +} + +int main(void) +{ + char testString[] = "catdog"; + + printString("The string '"); + printString(testString); + printString("' has length "); + + int l = stringLength(testString); + + printf("%d.",l); + + return 0; +} +``` + +The output is: + +``` +The string 'catdog' has length 6. +``` + +We've created a function for printing strings (`printString`) similar to `puts` and a function for computing the length of a string (`stringLength`). They both take as an argument a pointer to `char`, i.e. a string. In `printString` we use indexing (`[` and `]`) just as if `s` was an array, and indeed we see it works! In `stringLength` we similarly iterate over all characters in the string but we use dereference (`*s`) and pointer arithmetic (`s += 1;`). It doesn't matter which of the two styles we choose -- here we've shown both, for educational purposes. Finally notice that the string we actually work with is created in `main` as an array with `char testString[] = "catdog";` -- here we don't need to specify the array size between `[` and `]` because we immediately assign a string literal to it (`"catdog"`) and in such a case the compiler knows how big the array needs to be and automatically fills in the correct size. + +Now that know about pointers, we can finally completely explain the functions from `stdio` we've been using: + +- `int puts(char *s)`: A simple and fast function for printing a string (adds the newline character `\n` at the end). +- `int printf(char *format, ...)`: A little bit more complex function that can print not only strings but also other data types. It takes a variable number of parameters. The first one is always a string that specifies the print format -- this string can contain special sequences that will be replaced by textual representations of values we additionally provide as extra parameters after `format`. E.g. the sequence "%d" is replaced with a number obtained from the value of a corresponding `int` parameter. Similarly `%c` is for `char`, `%s` for strings, `%p` for pointers. Example: `printf("MyInt = %d, myChar = %c, MyStr = %s\n",myInt,myChar,myStr);`. +- `int getchar(void)`: Reads a single text character from the input and returns it. Why does the function return `int` and not `char`? Because the function can return additional special values such as `EOF` (end of file) which couldn't be stored in plain `char`. +- `int scanf(char *format, ...)`: Function for reading various data types from the input. Like `printf` it takes a variable number of parameters. The first one is a string that specifies which data type(s) to read -- this is a bit complicated but "%d" reads an `int`, "%f" `float`, "%c" `char` and "%s" string. The following arguments are **pointers** to expected data types, so e.g. if we've provided the format string "%d", a pointer to `int` has to follow. Through this parameter the value that's been read will be returned (in the same way we've seen in one example above). + +## Files + +Now we'll take a look at how we can read and write from/to file on the computer disk which allows us to store information permanently or potentially process data such as images or audio. Files aren't so difficult. + +We work with files through functions provide in the *stdio* library (so it has to be included). We distinguish two types of files: + +- **text files**: Contain text, are human readable. +- **binary files**: Contain binary data, aren't human readable, are more efficient but also more prone to corruption. + +From programmer's point of view there's actually not a huge difference between the two, they're both just sequences of characters or bytes (which are kind of almost the same). Text files are a little more abstract, they handle potentially different format of newlines etc. The main thing for us is that we'll use slightly different function for each type. + +There is a special data type for file called `FILE` (we'll be using a pointer to it). Whatever file we work with, we need to firstly open it with the function `fopen` and when we're done with it, we need to close it with a function `fclose`. + +First we'll write something to a text file: + +``` +#include + +int main(void) +{ + FILE *textFile = fopen("test.txt","w"); // "w" for write + + if (textFile != NULL) // if opened successfully + fprintf(textFile,"Hello file."); + else + puts("ERROR: Couldn't open file."); + + fclose(textFile); + + return 0; +} +``` + +When run, the program should create a new file named *test.txt* in the same directory we're in and in it you should find the text `Hello file.`. `FILE *textFile` creates a new variable `textFile` which is a pointer to the `FILE` data type. We are using a pointer simply because the standard library is designed this way, its functions work with pointers (it can be more efficient). `fopen("test.txt","w");` attempts to open the file *test.txt* in text mode for writing -- it returns a pointer that represents the opened file. The mode, i.e. text/binary, read/write etc., is specified by the second argument: `"w"`; *w* simply specifies *write* and the text mode is implicit (it doesn't have to be specified). `if (textFile != NULL)` checks if the file has been successfully opened; the function `fopen` returns `NULL` (the value of "point to nothing" pointers) if there was an error with opening the file (such as that the file doesn't exist). On success we write text to the file with a function `fprintf` -- it's basically the same as `printf` but works on files, so it's first parameter is always a pointer to a file to which it should write. You can of course also print numbers and anything that `printf` can with this function. Finally we mustn't forget to close the file at the end with `fclose`! + +Now let's write another program that read the file we've just created and writes its content out in the command line: + +``` +#include + +int main(void) +{ + FILE *textFile = fopen("test.txt","r"); // "r" for read + + if (textFile != NULL) // if opened successfully + { + char c; + + while (fscanf(textFile,"%c",&c) != EOF) // while not end of file + putchar(c); + } + else + puts("ERROR: Couldn't open file."); + + fclose(textFile); + + return 0; +} +``` + +Notice that in `fopen` we nowe specify `"w"` (write) as a mode. Again, we check if the file has been opened successfully (`if (textFile != NULL)`). If so, we use a `while` loop to read and print all characters from the file until we encounter the end of file. The reading of file characters is done with the `fscanf` function inside the loop's condition -- there's nothing preventing us from doing this. `fscanf` again works the same as `scanf` (so it can read other types than only `char`s), just on files (its first argument is the file to read from). On encountering end of file `fscanf` returns a special value `EOF` (which is macro constant defined in the standard library). Again, we must close the file at the end with `fclose`. + +We will now write to a binary file: + +``` +#include + +int main(void) +{ + unsigned char image[] = // image in ppm format + { + 80, 54, 32, 53, 32, 53, 32, 50, 53, 53, 32, + 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, + 255,255,255, 0, 0, 0, 255,255,255, 0, 0, 0, 255,255,255, + 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, + 0, 0, 0, 255,255,255, 255,255,255, 255,255,255, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,255,255 + }; + + FILE *binFile = fopen("image.ppm","wb"); + + if (binFile != NULL) // if opened successfully + fwrite(image,1,sizeof(image),binFile); + else + puts("ERROR: Couldn't open file."); + + fclose(binFile); + + return 0; +} +``` + +Okay, don't get scared, this example looks complex because it trying to do a cool thing: it creates an image file! When run, it should produce a file named *image.ppm* which is a tiny 5x5 smiley face image in [ppm](ppm.md) format. You should be able to open the image in any good viewer (I wouldn't bet on [Windows](windows.md) programs though). The image data was made manually and are stored in the `image` array. We don't need to understand the data, we just know we have some data we want to write to a file. Notice how we can manually initialize the array with values using `{` and `}` brackets. We open the file for writing and in binary mode, i.e. with a mode `"wb"`, we check the success of the action and then write the whole array into the file with one function call. The function is name `fwrite` and is used for writing to binary files (as opposed to `fprintf` for text files). `fwrite` takes these parameters: pointer to the data to be written to the file, size of one data element (in bytes), number of data elements and a pointer to the file to write to. Our data is the `image` array and since "arrays are basically pointers", we provide it as the first argument. Next argument is 1 (`unsigned char` always takes 1 byte), then length of our array (`sizeof` is a special operator that substitutes the size of a variable in bytes -- since each item in our array takes 1 byte, `sizeof(image)` provides the number of items in the array), and the file pointer. At the end we close the file. + +And finally we'll finish with reading this binary file back: + +``` +#include + +int main(void) +{ + FILE *binFile = fopen("image.ppm","rb"); + + if (binFile != NULL) // if opened successfully + { + unsigned char byte; + + while (fread(&byte,1,1,binFile)) + printf("%d ",byte); + + putchar('\n'); + } + else + puts("ERROR: Couldn't open file."); + + fclose(binFile); + + return 0; +} +``` + +The file mode is now `"rb"` (read binary). For reading from binary files we use the `fread` function, similarly to how we used `fscanf` for reading from a text file. `fread` has these parameters: pointer where to store the read data (the memory must have sufficient space allocated!), size of one data item, number of items to read and the pointer to the file which to read from. As the first argument we pass `&byte`, i.e. the address of the variable `byte`, next 1 (we want to read a single byte whose size in bytes is 1), 1 (we want to read one byte) and the file pointer. `fread` returns the number of items read, so the `while` condition holds as long as `fread` reads bytes; once we reach end of file, `fread` can no longer read anything and returns 0 (which in C is interpreted as a false value) and the loop ends. Again, we must close the file at the end. ## More on Functions (Recursion, Function Pointers) diff --git a/lrs.md b/lrs.md index 0a36e57..5df2e4c 100644 --- a/lrs.md +++ b/lrs.md @@ -1,6 +1,6 @@ # Less Retarded Software -Less retarded software (LRS) is a specific kind of [software](software.md) aiming to be a truly good technology maximally benefiting and respecting its users, following the philosophy of extreme [minimalism](minimalism.md) ([Unix philosophy](unix_philosophy.md), [suckless](suckless.md), [KISS](kiss.md)). The term was invented by [drummyfish](drummyfish.md). +Less retarded software (LRS) is a specific kind of [software](software.md) aiming to be a truly good technology maximally benefiting and respecting its users, following the philosophy of extreme [minimalism](minimalism.md) ([Unix philosophy](unix_philosophy.md), [suckless](suckless.md), [KISS](kiss.md)) and [freedom](free_software.md). The term was invented by [drummyfish](drummyfish.md). ## Definition @@ -20,7 +20,7 @@ The definition here is not strict but rather fuzzy, it is in a form of ideas, st - [Hacking](hacking.md) friendly and inviting to improvements and customization. - Built on top of other LRS technology such as the [C99](c.md) language, Unix OS, our own libraries etc. - Simple permissive licensing (being suckless legally) with great preference of [public domain](public_domain.md), e.g. with [CC0](cc0.md) + patent [waivers](waiver.md). -- Elegant by its simple, well thought-through solutions. +- Elegant by its simple, well thought-through solutions. (This is to be contrasted with modern rapid development.) - No [bullshit](bullshit.md) such as [codes of conduct](coc.md), tricky licensing conditions etc. ## Specific Software diff --git a/main.md b/main.md index 71debb9..33613fb 100644 --- a/main.md +++ b/main.md @@ -26,6 +26,7 @@ If you don't know where to start, here are some suggestions. If you're new, the - [suckless](suckless.md) - [less retarded software](lrs.md) - [capitalist software](capitalist_software.md) +- [C tutorial](c_tutorial.md) And if you just want something more obscure and [fun](fun.md), check out these: diff --git a/modern.md b/modern.md index d789752..1c48252 100644 --- a/modern.md +++ b/modern.md @@ -13,7 +13,7 @@ And before you say "it was faster and longer on battery etc. because it was simp - Old devices such as cell phones **lasted much, much longer on battery**. The old phones such as Nokia 3310 would **last long over a week** on stand-by. - **Old software was shipped finished, complete and with minimum [bugs](bug.md)**. Nowadays newly released "[apps](app.md)" and [games](game.md) are normally released unfinished, even in pre-alpha states and even "finished" ones have [bugs](bug.md) often rendering the software unsuable (see Cyberpunk 2077, GTA: Definiteve Edition etc.), user is supposed to wait years for fixes (without any guarantees), pay for content or even subscriptions. Old software was difficult or even impossible to patch (e.g. on [Gameboy](gameboy.md)) so it had to work. - **Old tech had minimum malicious features**. There wasn't [spyware in CPUs](inte_me.md), **[DRM](drm.md) was either absent or primitive**, there weren't ads in file explorers, there weren't [microtransactions](microtransaction.md) in games, there weren't [autoupdates](autoupdate.md), there weren't psychologically abusive [social networks](social_network.md), technology was **designed to last**, not to be [consoomed](consumerism.md), there was much less [censhorship](censorship.md). -- **Old tech was much easier to modify and customize**, thanks to not being so overcomplicated and not containing so many anti-repair "features". +- **Old tech was much easier to modify and customize**, thanks to not being so overcomplicated and not containing so many anti-repair "features". Old software wasn't in the [cloud](cloud.md) which makes it impossible to be modified. - **Old tech was much more independent**, did not require Internet connectivity, subscription etc. - There was **minimum [bullshit](bullshit.md)**. True usefulness was more important than killer features to help marketing. - Old tech was **simpler and more [fun](fun.md) to program**, allowing direct access to hardware, not complicating things with [OOP](oop.md) and similar [shit](shit.md), and so **old programmers were more [productive](productivity_cult.md)**. diff --git a/wikipedia.md b/wikipedia.md index 38ce234..6206876 100644 --- a/wikipedia.md +++ b/wikipedia.md @@ -2,7 +2,7 @@ TODO -# Good and Bad Things about Wikipedia +## Good and Bad Things about Wikipedia Let's note a few positive and negative points about Wikipedia, as of 2022. Some good things are: