Edited a lot, concatenated some comments, added pseudo-markdown stuff...
This commit is contained in:
parent
0eace4863a
commit
ce7d0a60cb
@ -6,10 +6,10 @@ And when you do redistribute it or modify it, it will use either version 3 of th
|
||||
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef CHAPTER_0_SOURCE
|
||||
#ifndef CHAPTER_0_SOURCE // These two, and "#endif" at the end of the file are header guards, we'll talk about them more when the time comes!
|
||||
#define CHAPTER_0_SOURCE
|
||||
|
||||
#include "chapter_0.h"
|
||||
#include "chapter_0.h" // We're pasting macros, enumerations and function declarations from header file "chapter_0.h" into this file, at this location.
|
||||
|
||||
/*
|
||||
Function 'in' will perform read system call, literally reading 'size' bytes from standard input, which is terminal in most kernels unless it's redirected to some other file
|
||||
@ -19,8 +19,8 @@ is 64 bits wide, maybe we made some other mistake? Just abort, find the error an
|
||||
*/
|
||||
|
||||
void in (void * data, int size) {
|
||||
fatal_failure (data == NULL, "in: Failed to read from standard input, data is null pointer.");
|
||||
fatal_failure (size == 0, "in: Failed to read from standard input, size is zero.");
|
||||
fatal_failure (data == NULL, "in: Failed to read from standard input, data is null pointer."); // This function is defined below, but we can call it here.
|
||||
fatal_failure (size == 0, "in: Failed to read from standard input, size is zero."); // That's because we declared it previously. Look at 'out' function.
|
||||
|
||||
(void) read (STDIN_FILENO, data, (unsigned long int) size); // I cast to void type return value of read and write, because I don't really care about it.
|
||||
}
|
||||
@ -29,7 +29,7 @@ void in (void * data, int size) {
|
||||
Similar to 'in' function and read system call, write system call will store 'size' bytes from 'data' memory address into standard output, which is usually what you see in your
|
||||
terminal. Now, I won't talk much about teletypes, virtual terminals, file descriptor redirections and similar stuff, because I'm not very knowledgable about them. What matters is,
|
||||
you have a working operating system, terminal and compiler, and you can make things happen. Once you learn C better than me, and start writing your own multi-threaded kernel, core
|
||||
libraries, compilers and what not, you'll care about those things.
|
||||
libraries, compilers and what not, you'll care about those things. I'll briefly talk about function structure for 'reallocate' function soon.
|
||||
*/
|
||||
|
||||
void out (void * data, int size) {
|
||||
@ -52,16 +52,16 @@ void echo (char * string) {
|
||||
void fatal_failure (int condition, char * message) { // We use this function to abort the program if condition is met and to print the message.
|
||||
if (condition != 0) { // If the variable 'condition' is not equal to 0, we execute the code in curly braces.
|
||||
echo ("[\033[1;31mExiting\033[0m] "); // Simply printing the message using our 'echo' function, but we also use some colours, more on that later.
|
||||
echo (message);
|
||||
echo ("\n");
|
||||
echo (message); // Also, notice how "this or that" is implicity 'char *' type... Maybe it's too early to explain it at this point.
|
||||
echo ("\n"); // This will only print a new line, we'll see how to use it later.
|
||||
exit (EXIT_FAILURE); // This is the function (and '_exit' system call) that aborts the program with a return code.
|
||||
} // If condition isn't met, function will just return, and nothing is printed, execution continues.
|
||||
}
|
||||
|
||||
void limit (int * value, int minimum, int maximum) { // This is somewhat similar to limiting a variable to some values, inclusively, we'll use it later.
|
||||
if (value == NULL) { return; } // We shouldn't dereference null pointer, but also don't want to abort the program for small mistake.
|
||||
if ( value == NULL) { return; } // We shouldn't dereference null pointer, but also don't want to abort the program for small mistake.
|
||||
if (* value <= minimum) { * value = minimum; } // You can also align similar consecutive statements like this, we'll see it more often in switch statement later on...
|
||||
if (* value >= maximum) { * value = maximum; }
|
||||
if (* value >= maximum) { * value = maximum; } // If we pass a null pointer to this function, it won't do anything, just return.
|
||||
}
|
||||
|
||||
/*
|
||||
@ -75,9 +75,8 @@ later in the program. It's important to "free" all "malloc/calloc/realloc"-ed me
|
||||
aborts. It's important for safety to do so, think of it like open and close braces, if you have some allocations, you should deallocate them later.
|
||||
|
||||
Some examples of using them directly (not wrapping them like I like to do) are:
|
||||
*/
|
||||
|
||||
/**
|
||||
@C
|
||||
char * data = NULL;
|
||||
|
||||
data = malloc (20 * sizeof (* data)); // Allocates 20 bytes of memory for 'data'.
|
||||
@ -85,7 +84,8 @@ data = calloc (20, sizeof (* data)); // Allocates 20 bytes also, but ini
|
||||
data = realloc (data, 20 * sizeof (* data)); // When 'data' is null pointer, it will be same as 'malloc', else it will reallocate more memory (for correct usage).
|
||||
|
||||
free (data); // Deallocates memory, we'll talk about "double free" later.
|
||||
**/
|
||||
@
|
||||
*/
|
||||
|
||||
void * allocate (int size) {
|
||||
char * data = NULL;
|
||||
@ -97,6 +97,13 @@ void * allocate (int size) {
|
||||
return ((void *) data);
|
||||
}
|
||||
|
||||
/*
|
||||
Now, lets see that code formatting in action, with briefly describing function structure in C programming language. Our function is called "reallocate", its' inputs (arguments)
|
||||
are "data" with type 'void *' (pointer to any type of memory address) and "size" with type 'int' (integer), and its' output is also 'void *' (some memory address). All code
|
||||
between first '{' and last connected '}' is part of that function. We're using function 'realloc' inside, but we check for error (it return 'NULL' on error), then we print message
|
||||
and abort the program if there was an error, it there wasn't, we return new enlarged chunk of memory, changing the "data" variable.
|
||||
*/
|
||||
|
||||
void * reallocate (void * data, int size) {
|
||||
data = realloc (data, (unsigned long int) size);
|
||||
|
||||
@ -113,4 +120,16 @@ void * deallocate (void * data) {
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
int string_length (char * string) {
|
||||
int length;
|
||||
|
||||
if (string == NULL) {
|
||||
return (0);
|
||||
}
|
||||
|
||||
for (length = 0; string [length] != '\0'; ++length);
|
||||
|
||||
return (length);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -10,31 +10,31 @@ It is distributed in the hope that it will be useful or harmful, it really depen
|
||||
#define CHAPTER_0_HEADER
|
||||
|
||||
/*
|
||||
> Code formatting
|
||||
|
||||
It's very important that your code is aligned, readable and safe. So, we'll talk about it even before the first chapter, the so-called "zero chapter". I write assembly and Ada
|
||||
beside C, but still, 90% of my projects are written in C programming language! Why? Because C is like a girl that's a bit older than you, quiet but very smart and respectable, and
|
||||
you don't want to piss her off. Ada is more like nerdy girl who doesn't know what she wants, while assembly is ghost girl in your closet that you pretend not to see at night. So,
|
||||
this Ada girl was my first serious love, and she had a book "Ada Coding Standard", which is like a bible to her. It's a bit fanatical, but it has some good points which I'll
|
||||
discuss below without copy/paste, and apply to C.
|
||||
*/
|
||||
|
||||
/*
|
||||
Lets see some good examples of C code (in my opinion obviously), I'll provide few of them, and note that I use the first style in them. Needless to say, as this book is aimed to
|
||||
people who don't know C programming language, or are in phase of learning it, you should (and eventually will) develop your own style of writing it, but you shouldn't force it at
|
||||
the start, it should happen over time, naturally. Just be sure to always be consistent, and to avoid anything that'll generate compiler warnings.
|
||||
*/
|
||||
|
||||
/*
|
||||
General code formatting should follow these rules in C programming language:
|
||||
|
||||
- Always use tabs for indentation, and spaces for alignment. You'll hear people say the otherwise, don't trust them, it's pure cancer.
|
||||
- Separate most (not all!) operators with space before and after it. We'll provide exceptional cases for this rule below.
|
||||
- Align most (not all!) operators horizontally, and logical units vertically. Again, examples are the best showcase.
|
||||
- Always use curly braces and round braces, even when they're not required by the compiler. Most linters warn about this.
|
||||
- Avoid writing a lot of comments, or avoid them completely, except for license notice, this book is an exception!
|
||||
- Avoid writing a lot of comments, or avoid them completely, except for license notice, this book is an exception! Also, don't write GPL notice like I do...
|
||||
- Think very carefully if you'll limit the line length, it depends on your workflow and attention span... For me, 180 characters per line.
|
||||
- If you want, use pagination, but I don't recommend it when working with C. I use it in Ada, because comments there start with "--".
|
||||
- Always use snake case (this_style), for enumerations and macros (THIS_STYLE), and never put underscore before or after the identifier.
|
||||
|
||||
For now, don't worry about what's an identifier, compiler, linter, those scary and unknown words will be explained later, when the time comes.
|
||||
|
||||
Spacing:
|
||||
|
||||
- Before and after:
|
||||
@ -47,7 +47,7 @@ Spacing:
|
||||
% %= >> >>= : <
|
||||
= ~ >
|
||||
|
||||
A) Standard operators, addition, subtraction, multiplication, division, modulus and assignment.
|
||||
A) Standard operators, addition, subtraction, multiplication, division, modulus and assignment (except assignment obviously).
|
||||
B) Extension of standard operators, that'll use left-hand variable as first operand.
|
||||
C) Binary operators, bit-and, bit-or, bit-xor, bit-shift-left, bit-shift-right and bit-not.
|
||||
D) Again, same extension but for binary operators, that'll use left-hand variable as first operand (except bit-not as it's unary).
|
||||
@ -59,6 +59,7 @@ Some more symbols are also operators, but my advice is not to treat them as such
|
||||
- Before:
|
||||
|
||||
( { [
|
||||
|
||||
- After:
|
||||
|
||||
) } ] , ;
|
||||
@ -69,7 +70,8 @@ Some more symbols are also operators, but my advice is not to treat them as such
|
||||
|
||||
Exceptions:
|
||||
|
||||
- Merge consecutive opened or closed braces, for example, like when you're nesting function calls.
|
||||
- When using more complicated expressions, apply rules above, or make an exception in them if you think you'll make it more readable by breaking the rules.
|
||||
- When merging consecutive opened or closed braces, for example, like when you're nesting function calls.
|
||||
- When declaring functions, align return types and open braces, and by taste, argument types for logical units.
|
||||
- When declaring variables, align types and asignment operators, and by taste, pointer or array operators.
|
||||
|
||||
@ -85,16 +87,15 @@ consistent. I can't handle them, so I tolerate them with C keywords, because I'd
|
||||
C has mixed them in keywords, and most standard library headers mix them too...
|
||||
|
||||
- Keywords: register double short signed break while / int char extern auto enum struct
|
||||
- Headers: string memory limits time complex threads / stdio stdlib stdint unistd fcntl termios
|
||||
- Headers: string memory limits time complex threads / stdio ctype stdint unistd fcntl termios
|
||||
- Functions: read write open close exit clock / strncmp memcpy atoi fputc tcsetattr usleep
|
||||
|
||||
About that, I don't even know where to begin... But lets ignore that for now and focus on code formatting...
|
||||
About that, I don't even know where to begin about talking how bad it is for readability... But lets ignore that for now and focus on code formatting.
|
||||
|
||||
There's not much to say here, we'll talk about function declarations more in later chapters, as they are optional, but I like to use them. However, you should avoid to use too
|
||||
many function agruments, and always place them in consistent and logical order.
|
||||
*/
|
||||
|
||||
/**
|
||||
@C
|
||||
// Internal and external function declaration:
|
||||
|
||||
static int string_copy_limit (char * string_0, char * string_1, int limit);
|
||||
@ -107,15 +108,13 @@ extern size_t copy_string_n (
|
||||
string_t a,
|
||||
string_t b,
|
||||
size_t n);
|
||||
**/
|
||||
@
|
||||
|
||||
/*
|
||||
Internal ('static') and external ('extern') variable declarations are different from function declarations, because you need to initialize internal variables to some value, I like
|
||||
to provide either "zero" value for them, with the corresponding type (0, 0.0, '\0', NULL...), or an invalid value (-1, EOF...). Some C compilers and linters will warn you about
|
||||
redeclaring them, some will warn you that they're already initialized to zero, so when you some across those warnings, you'll know how to fix them. I hate this part of C...
|
||||
*/
|
||||
|
||||
/**
|
||||
@C
|
||||
// Internal and external variable declaration:
|
||||
|
||||
static int curses_active = 0;
|
||||
@ -136,15 +135,13 @@ extern xcb_pixmap_t blesses_pixmap;
|
||||
extern xcb_connection_t * blesses_connection;
|
||||
extern xcb_screen_t * blesses_screen;
|
||||
extern xcb_image_t * blesses_image;
|
||||
**/
|
||||
@
|
||||
|
||||
/*
|
||||
Only for 'main' function, you shouldn't add 'static' or 'extern' keywords before it. Every C file needs to have exactly one 'main' function, it's the programs' entry point. Also,
|
||||
some people like to define their own types, with keyword 'typedef', I'm not a fan of it, since in C types are also keywords unlike in Ada. Again, it'll become more clean in future
|
||||
chapters why I dislike user-defined types, unions and structures.
|
||||
*/
|
||||
|
||||
/**
|
||||
@C
|
||||
// Main function:
|
||||
|
||||
int main (int argc, char * * argv) {
|
||||
@ -155,18 +152,17 @@ int32_t main (int32_t argc,
|
||||
number_t main (
|
||||
number_t argc,
|
||||
string_t argv []) {
|
||||
**/
|
||||
@
|
||||
|
||||
/*
|
||||
Now, I'll write some basic functions that'll be used later in the program, so you can see that code formatting I've spent whole hour writing. Don't mind what they do, just keep an
|
||||
eye on how are they aligned and named. I'll reimplement some standard functions, and you can revisit them after reading few more chapters, it'll be more understandable then.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h> // We'll need this header file for malloc, calloc, realloc, free and exit.
|
||||
#include <fcntl.h> // This one for open and O_ flags.
|
||||
#include <unistd.h> // And this one for read, write, close and lseek.
|
||||
|
||||
#define SIGNAL_ARROW_UP (0X415B1B)
|
||||
#define SIGNAL_ARROW_UP (0X415B1B) // Just hardcoding some arrow key ASCII escape sequences, we'll explain them in later chapters...
|
||||
#define SIGNAL_ARROW_DOWN (0X425B1B)
|
||||
#define SIGNAL_ARROW_RIGHT (0X435B1B)
|
||||
#define SIGNAL_ARROW_LEFT (0X445B1B)
|
||||
@ -176,7 +172,7 @@ enum { // I like to align enumerations with 10, 20 or 40 characters per name, an
|
||||
EFFECT_COUNT
|
||||
};
|
||||
|
||||
enum { // Because of text auto-completition, it's always easy to find what I want, or to use NAME_COUNT in arrays.
|
||||
enum { // Because of text auto-completition, it's always easy to find what I want, or to use NAME_COUNT in arrays. In some cases, order matters!
|
||||
COLOUR_GREY, COLOUR_RED, COLOUR_GREEN, COLOUR_YELLOW, COLOUR_BLUE, COLOUR_PINK, COLOUR_CYAN, COLOUR_WHITE,
|
||||
COLOUR_COUNT
|
||||
};
|
||||
@ -206,4 +202,6 @@ extern void * allocate ( int size); // Core memory management func
|
||||
extern void * reallocate (void * data, int size);
|
||||
extern void * deallocate (void * data );
|
||||
|
||||
extern int string_length (char * string); // We deal with strings a lot in this program, so string functions will be more important than character functions from chapter one.
|
||||
|
||||
#endif
|
||||
|
@ -9,6 +9,8 @@ It is distributed in the hope that it will be useful or harmful, it really depen
|
||||
#ifndef CHAPTER_1_SOURCE
|
||||
#define CHAPTER_1_SOURCE
|
||||
|
||||
#include "chapter_1.h"
|
||||
|
||||
int character_is_uppercase (char character) {
|
||||
return ((int) ((character >= 'A') && (character <= 'Z')));
|
||||
}
|
||||
@ -22,7 +24,22 @@ int character_is_digit (char character) {
|
||||
}
|
||||
|
||||
int character_is_blank (char character) {
|
||||
return ((int) ((character == ' ') || (character == '\t') || (character == '\r') || (character == '\n')));
|
||||
return ((int) ((character == ' ') || (character == CHARACTER_TAB_HORIZONTAL) || (character == CHARACTER_CARRIAGE_RETURN) || (character == CHARACTER_LINE_FEED)));
|
||||
// If you like smaller line length limit, you can align it like this:
|
||||
// return ((character == ' ')
|
||||
// || (character == CHARACTER_TAB_HORIZONTAL)
|
||||
// || (character == CHARACTER_CARRIAGE_RETURN)
|
||||
// || (character == CHARACTER_LINE_FEED));
|
||||
// Or:
|
||||
// return ((character == ' ') ||
|
||||
// (character == CHARACTER_TAB_HORIZONTAL) ||
|
||||
// (character == CHARACTER_CARRIAGE_RETURN) ||
|
||||
// (character == CHARACTER_LINE_FEED));
|
||||
// Or even use literal characters:
|
||||
// return ((character == ' ') ||
|
||||
// (character == '\t') ||
|
||||
// (character == '\r') ||
|
||||
// (character == '\n'));
|
||||
}
|
||||
|
||||
int character_is_alpha (char character) {
|
||||
@ -56,7 +73,7 @@ int character_is_hexadecimal (char character) {
|
||||
int character_compare_array (char character, char * character_array) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i != string_length (character_array); ++i)
|
||||
for (i = 0; i != string_length (character_array); ++i) {
|
||||
if (character == character_array [i]) {
|
||||
return (1);
|
||||
}
|
||||
|
@ -9,10 +9,13 @@ It is distributed in the hope that it will be useful or harmful, it really depen
|
||||
#ifndef CHAPTER_1_HEADER
|
||||
#define CHAPTER_1_HEADER
|
||||
|
||||
#include "chapter_0.h" // We need this header file for some core enumeration definitions and function declarations.
|
||||
|
||||
/*
|
||||
Now that you've read the chapter zero, we should get into some technical details about C programming language. This will be the most imporant chapter for people who know some
|
||||
other lower level programming language, or who intuitively understand "building blocks" of some system. Below is just a simple matrix of them, 8 x 4, so you can see that there are
|
||||
really 32 keywords (ANSI C standard, I dislike newer standards), and even more below we'll categorize them.
|
||||
really 32 keywords (ANSI C standard, I dislike newer standards), and even more below we'll categorize them. Also, keep in mind that I'll briefly talk about other C standards such
|
||||
as K&R, C99, etc., and use parts of them in some places, so you don't get confused when you see them in other peoples' source code.
|
||||
|
||||
- Keywords:
|
||||
|
||||
@ -34,7 +37,7 @@ Keywords that you should never use, under any circumstances (except for writing
|
||||
- union: You'll have very little excuse to use unions, because they often don't go well with type checking and introduce complexity for no reason.
|
||||
- goto: I'm kidding, feel free to use 'goto' statement, it's not harmful, some people just abused it to the point of it being bullied.
|
||||
|
||||
Keywords that you should really consider not to use in general, but only for some very specific cases:
|
||||
Keywords that you should really consider not to use in general, but only for some very specific cases (compiler warnings or using some libraries):
|
||||
|
||||
- const: Constant qualifier sometimes tells to the compiler that a value won't be changed. Use it only to silence the compiler warnings...
|
||||
- unsigned: Again, you shouldn't care if your variable is signed or unsigned, because C really likes integers, and hates naturals. We'll talk more about it.
|
||||
@ -72,13 +75,22 @@ see that sometimes is preferable to use switch statement somewhere, or while loo
|
||||
library that internally used structures everywhere, so you'll need to adapt to it, we'll see examples later...
|
||||
|
||||
So, real men need these keywords { char, int, void, sizeof, static, if, else, for, enum, return }, and use the rest of them in order to silence compiler warnings, use some
|
||||
standard library functions, clean the source code or access an API / library / header file.
|
||||
standard library functions, clean the source code or access an API / library / header file. Lets see some more examples and keep in mind code formatting...
|
||||
|
||||
@C
|
||||
extern int this_is_kind (kind_type this);
|
||||
// extern - we declare this function as external one, because we'll create an object file (.o), and link it with other programs (or object files).
|
||||
// int - we set 'int', integer as return type of this function, and use 0 as false, and 1 as true, instead of 'bool' type from <stdbool.h>.
|
||||
// this_is_kind - we named our function like this, you'll see more of verbose naming when I write programs, because I think it's better...
|
||||
// kind_type - we choose the type of arguments in our function, since our function deals with 'kind', we choose proper 'kind_type'.
|
||||
// this - we named our argument about what's it supposed to be, use similar approach in return type, function, argument type and argument names.
|
||||
@
|
||||
*/
|
||||
|
||||
extern int character_is_uppercase (char character);
|
||||
extern int character_is_lowercase (char character);
|
||||
extern int character_is_digit (char character);
|
||||
extern int character_is_blank (char character);
|
||||
extern int character_is_uppercase (char character); // Notice how we align those functions, I believe this improves the readability of any program, in any programming language.
|
||||
extern int character_is_lowercase (char character); // Some people would just use 'ischrupp' or something, but I hate reading code written like that...
|
||||
extern int character_is_digit (char character); // Important note is also that a programming language is not, and it should be like natural language, why?
|
||||
extern int character_is_blank (char character); // Because we need strict rules in programming language, same like in mathematical languages, now now, don't be scared.
|
||||
extern int character_is_alpha (char character);
|
||||
extern int character_is_symbol (char character);
|
||||
extern int character_is_visible (char character);
|
||||
@ -87,6 +99,6 @@ extern int character_is_escape (char character);
|
||||
extern int character_is_underscore (char character);
|
||||
extern int character_is_hexadecimal (char character);
|
||||
|
||||
extern int character_compare_array (char character, char * character_array);
|
||||
extern int character_compare_array (char character, char * character_array); // This function is singled out, because it's different from those above, and we use it internally.
|
||||
|
||||
#endif
|
||||
|
@ -9,24 +9,22 @@ It is distributed in the hope that it will be useful or harmful, it really depen
|
||||
#ifndef CHAPTER_2_SOURCE
|
||||
#define CHAPTER_2_SOURCE
|
||||
|
||||
#include "chapter_2.h" /* We're copying function and variable declarations from this file here. This shouldn't be copied twice, more on that later... */
|
||||
#include "chapter_2.h" // We're copying function and variable declarations from this file here. This shouldn't be copied twice, more on that later...
|
||||
|
||||
/*
|
||||
Function 'hello_world_0' uses (rougly said) a system call instead of a function, but I don't even know what will different compilers do to that line of code. What's a system call?
|
||||
Well, when you're writing assembly, you have some general purpose registers, on mine and your machine, there's 16 of them, and you move some data in them, then use 'syscall'
|
||||
instruction, which magically does something, like open or close a file descriptor, read or write to it, but more on that later! A lot more... Important thing to know is that when
|
||||
you use keyword 'sizeof' on a string, it'll count null terminator, so it'll have value 14, and type 'size_t'. It's provided in <unistd.h> header file.
|
||||
*/
|
||||
|
||||
/**
|
||||
@C
|
||||
write ( // mov rax 1 ; Literal of Linux write system call.
|
||||
STDOUT_FILENO, // mov rdi 1 ; Literal of standard output file descriptor.
|
||||
"Hello world!\n", // mov rsi X ; Address of our string.
|
||||
sizeof ("Hello world!\n") // mov rdx [Y] ; Literal of size of our string.
|
||||
); // syscall ; Ask kernel to do some work.
|
||||
**/
|
||||
@
|
||||
|
||||
/*
|
||||
We'll talk about assembly a lot in future chapters, but you should filter that out of your brain until you learn C well...
|
||||
*/
|
||||
|
||||
@ -69,6 +67,27 @@ Lastly, we have a global variable, some people like to call it that, and conside
|
||||
overhead for certain sort of people. Pros, makes your code shorter and easier to refactor. Cons, it can potentially make your code less safe if you don't know what you're doing.
|
||||
You should use them, external variables as I like to call them, when you're working on your programs or in a small group, but they can lead to bad things if you're working with a
|
||||
lot of people, someone could change it in some bad place, and cause a bug that's difficult to track.
|
||||
|
||||
We could initialize it also in any part of our code later, in some function for example, as following:
|
||||
|
||||
@C
|
||||
// One by one:
|
||||
hello_world [0] = hello_world_0;
|
||||
hello_world [1] = hello_world_1;
|
||||
hello_world [2] = hello_world_2;
|
||||
hello_world [3] = hello_world_3;
|
||||
|
||||
// Or use it for some completely unrelated function if we wanted to, as long as function "kind" is same, they both take no input and no output:
|
||||
|
||||
static void unrelated_function (void) {
|
||||
puts ("I couldn't care less...");
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
hello_world [2] = unrelated_function;
|
||||
@
|
||||
|
||||
Just know that an array(s) of function pointers is my favorite kind of variable for bot AI in simple games, index can be derived from bot state.
|
||||
*/
|
||||
|
||||
void (* hello_world [4]) (void) = {
|
||||
|
@ -14,8 +14,8 @@ This is probably the first program new programmers write in language they're lea
|
||||
lot of ways to do it, so we'll simply put all of them into array of function pointers, and call them sequentially in loop in our main function.
|
||||
*/
|
||||
|
||||
#include <stdio.h> /* We need this header file for functions 'puts' and 'printf'. */
|
||||
#include <unistd.h> /* And in this header file we have write system call. */
|
||||
#include <stdio.h> // We need this header file for functions 'puts' and 'printf'.
|
||||
#include <unistd.h> // And in this header file we have write system call.
|
||||
|
||||
/*
|
||||
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
|
||||
@ -23,9 +23,8 @@ quickly prototyping something out. Function declarations are property of old pro
|
||||
(but not declared, there's a difference), but if that function is only used in one file, you should use 'static' keyword like in the example below. Keyword 'extern' tells the
|
||||
compiler that your function will be used in other files, or if you're compiling your files separately, it won't add the function address (in some cases). In any case, function
|
||||
declarations should be in C header file, and function definitions should be in C source file.
|
||||
*/
|
||||
|
||||
/**
|
||||
@C
|
||||
// Function declaration: Output: Input:
|
||||
extern void function_0 (void); // undefined (none) // undefined (none)
|
||||
extern float function_1 (double a); // ieee754_32b // ieee754_64b
|
||||
@ -35,10 +34,10 @@ static char * function_4 (char * a); // integer_8b
|
||||
static void * function_5 (struct structure * a, void * b); // undefined_pointer // [structure]_pointer, undefined_pointer
|
||||
|
||||
extern unsigned long int * PLEASE_NO (const signed short int, void *, const char const * *, long double []);
|
||||
**/
|
||||
@
|
||||
|
||||
/*
|
||||
So, lets go through those 6 normal functions, and then lets see what's wrong with 'PLEASE_NO' function.
|
||||
|
||||
0) This is the simplest form of function, which can be used to make the code more clear (sometimes) by breaking down long functions into smaller ones, and calling them in certain
|
||||
order. However, don't abuse it for code refactoring, because most of the time, procedural is the easiest to read, write and debug.
|
||||
1) In C (and in non-embedded environment, also known as your normal post-2012 laptop or computer), keyword 'float' is (almost) real number encoded in IEEE754, 32 bits or 4 bytes
|
||||
@ -47,7 +46,9 @@ long, while keyword 'double' is same as float, but enlarged to 64 bits or 8 byte
|
||||
3)
|
||||
4)
|
||||
5)
|
||||
|
||||
Now, lets talk very briefly about what's wrong with 'PLEASE_NO':
|
||||
|
||||
- Don't use long types 'unsigned long int', if you really type with those characteristics, use 'size_t : stdlib', 'uint64_t : stdint' or 'unsigned long'.
|
||||
- Don't use 'const' keyword at all, that's my honest advice, it won't catch bugs in C, it'll cause useless compiler warnings. Live unsafe, think more.
|
||||
- Don't use 'signed', since it's default for any integer type, as well as you shouldn't use keywords 'register', 'volatile', 'auto', but more on that later...
|
||||
@ -55,11 +56,11 @@ Now, lets talk very briefly about what's wrong with 'PLEASE_NO':
|
||||
- Keep in mind that newer (non-ANSI) C standards allow some of the things that I'm not using here, but I don't personally like newer C standards.
|
||||
*/
|
||||
|
||||
extern void hello_world_0 (void); /* All 4 functions take no agruments, and return nothing. They just execute the code that's defined in them. */
|
||||
extern void hello_world_0 (void); // All 4 functions take no agruments, and return nothing. They just execute the code that's defined in them.
|
||||
extern void hello_world_1 (void);
|
||||
extern void hello_world_2 (void);
|
||||
extern void hello_world_3 (void);
|
||||
|
||||
extern void (* hello_world [4]) (void); /* External (global) variable with name 'hello_world' of type 'array of 4 function pointers with no input/output'. */
|
||||
extern void (* hello_world [4]) (void); // External (global) variable with name 'hello_world' of type 'array of 4 function pointers with no input/output'.
|
||||
|
||||
#endif
|
||||
|
@ -2,6 +2,12 @@
|
||||
|
||||
set -xe
|
||||
|
||||
gcc -Wall -Wextra -Wpedantic -Werror -Ofast -o xhartae xhartae.c
|
||||
gcc -Wall -Wextra -Wpedantic -Werror -Ofast -c -o chapters/chapter_0.o chapters/chapter_0.c
|
||||
gcc -Wall -Wextra -Wpedantic -Werror -Ofast -c -o chapters/chapter_1.o chapters/chapter_1.c
|
||||
gcc -Wall -Wextra -Wpedantic -Werror -Ofast -c -o chapters/chapter_2.o chapters/chapter_2.c
|
||||
|
||||
gcc -Wall -Wextra -Wpedantic -Werror -Ofast -c -o xhartae.o xhartae.c
|
||||
|
||||
gcc -o xhartae xhartae.o chapters/chapter_0.o chapters/chapter_1.o chapters/chapter_2.o
|
||||
|
||||
exit
|
||||
|
21
xhartae.c
21
xhartae.c
@ -6,8 +6,12 @@ And when you do redistribute it or modify it, it will use either version 3 of th
|
||||
It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3.
|
||||
*/
|
||||
|
||||
#include "chapters/chapter_0.h"
|
||||
#include "chapters/chapter_1.h"
|
||||
#include "chapters/chapter_2.h"
|
||||
|
||||
/*
|
||||
Maybe title:
|
||||
Maybe title (work in progress):
|
||||
- The Great C & Practical Sailing
|
||||
- The Ultimate C Programming Language Guide
|
||||
- You Will Never Be A Book
|
||||
@ -20,11 +24,13 @@ this "book" will be full of grammatical mistakes, but not compiler warnings. Ple
|
||||
rambling and focus on what's written outside of the comments, you'll easily learn it. Good luck and have fun...
|
||||
|
||||
Why should you learn or use C programming language in 2023?
|
||||
|
||||
- C was inspiration for many newer programming languages for good reasons.
|
||||
- C can interface with huge variety of other distinct programming languages.
|
||||
- C can be a lot more readable, faster and easier if used well.
|
||||
|
||||
One sane C program should have the following structure:
|
||||
|
||||
0) Optional file, author or license information in comment.
|
||||
1) Header guards and implementation definitions.
|
||||
2) System header files then project header files.
|
||||
@ -34,15 +40,12 @@ One sane C program should have the following structure:
|
||||
6) Internal function then variable definition.
|
||||
7) External function then variable definition.
|
||||
8) Main function.
|
||||
*/
|
||||
|
||||
/*
|
||||
In C language, we have C source files with the extension '.c', and C header files with the extension '.h'. Both of those are just plain text files, and please use 7-bit ASCII
|
||||
encoding, since it's common sense, UTF is cancer, and 8-bit ASCII is for enlightened people like Terrence Andrew Davis. C language is completely separate (on some C compilers)
|
||||
from its' preprocessor, whose directives start with '#' character, continue on '\' character and break on '\n' (read: LINE FEED) character.
|
||||
*/
|
||||
|
||||
/**
|
||||
@C
|
||||
#include <path/to/file/file_name.h> // Copy the entire file from '/usr/include/' directory into this file, on the place where it was specified.
|
||||
#include "path/to/file/file_name.h" // Copy the entire file from current directory into this file, again on the place where it was specified.
|
||||
|
||||
@ -54,12 +57,8 @@ from its' preprocessor, whose directives start with '#' character, continue on '
|
||||
#ifndef SOMETHING //
|
||||
#else //
|
||||
#endif //
|
||||
**/
|
||||
|
||||
#include <stdlib.h> /* This header is for functions malloc, calloc, realloc, free. */
|
||||
#include <unistd.h> /* And this header if for functions (system calls) read, write, open, close, exit... */
|
||||
|
||||
#include "chapters/chapter_2.c"
|
||||
@
|
||||
*/
|
||||
|
||||
int main (int argc, char * * argv) {
|
||||
int i;
|
||||
|
Loading…
Reference in New Issue
Block a user