xhartae/chapter/chapter_0.h

270 lines
17 KiB
C

#ifndef CHAPTER_0_HEADER
#define CHAPTER_0_HEADER
/*
In this chapter, you'll learn about:
- Code formatting
- Operators
- Function and variable declarations
- Enumerations
- Some basic functions
- String and memory functions
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, they don't know.
- 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! 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.
+ Feel free the break the rules if, by your jugdement, that will improve readability or emphasize the intention, I break them too.
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:
A: B: C: D: E: F:
+ += & &= && ==
- -= | |= || !=
* *= ^ ^= ! <=
/ /= << <<= ? >=
% %= >> >>= : <
= ~ >
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).
E) Logical operators, and, or, not and operators '?' and ':' are used together, as they form ternary operator.
F) Comparison operators, equals, not-equals, less-or-equal, greater-or-equal, less and greater.
Some more symbols are also operators (and some are not), but my advice is not to treat them as such, just consider them some kind of special cases.
- Before:
( { [
- After:
) } ] , ;
- None (unary + and -):
. -> " ' ++ -- + -
Exceptions:
- 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.
Naming:
- Have you ever wondered, what the hell is stdio, unistd, stdint, termios, errno, strcat, strchr, strndupa, memcpy, atoi, fgetc, puts, lseek...? I did wonder.
- Are identifiers such as string_concatenate, memory_copy, error_id, integer, boolean, constant, string_to_integer, file_offset too long to type...? I think not.
All that aside, C is an old programming language, and people who have made it knew a lot more about how computers work at their time than most people know nowdays. Hardware got
faster and cheaper, but also more complex. We have enough memory today to edit our text files with word-wrapping enabled, highlight the syntax, and to store large files due to
very long identifiers, 8-space-wide indentation instead of a single tab and overly complicated OOP code. If you can handle those names above, feel free to use them, but be
consistent. I can't handle them, so I tolerate them with C keywords, because I'd actually prefer "constant / integer / character" over "const / int / char"... You'll notice that
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 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 about talking how bad it is for readability... But lets ignore that for now and focus on code formatting. Text you see above this
paragraph is enough to get you started, and text below is just expanding a little more on common sense when you're formatting your code. For example, how to align consequtive
external or internal function or variable declarations, how to align if statement condition(s) or switch statement body, and more.
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 * destination, char * source, int limit);
extern int32_t string_copy (char * string_a,
char * string_b,
int32_t count);
extern size_t copy_string_n (
string_t a,
string_t b,
size_t n
);
// And you can improve last 2 examples by using 'destination' for 'string_0/a' and 'source' for 'string_1/b', since it's more clear what the function does.
@
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; // You can declare and define internal variables right away, all in one line.
static char curses_signal = '\0';
static char * curses_screen = NULL;
extern unsigned int positive; // You need to declare external variables without initializing them immediately.
extern int negative;
extern int * * coordinate_system;
unsigned int positive = +1; // And then to define them separately, unlike internal variables.
int negative = -1;
int * * coordinate_system = NULL;
static void (* encode_type_0 [TYPE_0_COUNT]) (void) = { NULL }; // We're declaring arrays of function pointers this way, we'll cover them in later chapters.
static char * (* encode_type_1 [TYPE_1_COUNT]) (int l) = { NULL };
static int (* encode_type_2 [TYPE_2_COUNT]) (int l, int r) = { NULL };
extern xcb_window_t blesses_window;
extern xcb_gcontext_t blesses_context;
extern xcb_pixmap_t blesses_pixmap;
extern xcb_connection_t * blesses_connection;
extern xcb_screen_t * blesses_screen;
extern xcb_image_t * blesses_image;
xcb_window_t blesses_window; // Here, same example for external variables, but you don't even need to initialize them.
xcb_gcontext_t blesses_context; // If you know what you're doing, and when they'll be initialized, you can leave them like this.
xcb_pixmap_t blesses_pixmap;
xcb_connection_t * blesses_connection;
xcb_screen_t * blesses_screen;
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. That doesn't mean we won't use them tho, because I need to show-case their usage. Also, as for main function, it
can have either 'void' as argument, so the program takes no command-line arguments, or 'int argc' and 'char * * argv / char * argv []'.
@C
// Main function:
int main (void) {
int32_t main (int32_t argc,
char * argv []) {
// Somewhere before, with using 'typedef':
// typedef int number_t;
// typedef char * string_t;
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.
Also, what is keyword 'enum' for? It's for enumerations, and early C programming language didn't have it. It assigns a value to some identifier, if not specified, it's automatic:
@C
// Values of A, B and C are same for these 3 enumerations.
enum { A, B, C };
enum { A = 0, B = 1, C };
enum { A = 0, B = 1, C = 2 };
// However, if you use:
enum { A = 1, B, C };
// Then, A is 1, B is 2 and C is 3. Easy, right? Also:
enum { A, B, C = 0, D };
// In this last case, A and C are 0, B and D are 1.
@
By default, it always starts from zero, if you assign some value to it (must be integer), next one will be increment of previous value, if otherwise not specified.
*/
enum {
// This is completely unnecesary, but I don't care, it's a good showcase how boolean type can work, true is 1 and false is 0.
FALSE,
TRUE
};
enum {
// I use these to explicitly identify character literals outside of strings.
// This is also one of my preferences, to use CHARACTER_NULL or CHARACTER_LINE_FEED instead of '\0' or '\n' in special (non-string) cases.
// I align them like this, you can do whatever you want here, I just don't like to write a lot of short lines of code...
// I align long identifiers by 40 characters + indentation width (denoted with T). I won't align next two comments, to show you the offset.
// T + 0 * 40 T + 1 * 40 T + 2 * 40 T + 3 * 40
// ^ ^ ^ ^
CHARACTER_NULL, CHARACTER_START_HEADER, CHARACTER_START_TEXT, CHARACTER_END_TEXT,
CHARACTER_END_TRANSMISSION, CHARACTER_ENQUIRY, CHARACTER_ACKNOWLEDGE, CHARACTER_BELL,
CHARACTER_BACKSPACE, CHARACTER_TAB_HORIZONTAL, CHARACTER_LINE_FEED, CHARACTER_TAB_VERTICAL,
CHARACTER_FORM_FEED, CHARACTER_CARRIAGE_RETURN, CHARACTER_SHIFT_OUT, CHARACTER_SHIFT_IN,
CHARACTER_DATA_LINK_ESCAPE, CHARACTER_DEVICE_CONTROL_1, CHARACTER_DEVICE_CONTROL_2, CHARACTER_DEVICE_CONTROL_3,
CHARACTER_DEVICE_CONTROL_4, CHARACTER_NOT_ACKNOWLEDGE, CHARACTER_SYNCHRONOUS_IDLE, CHARACTER_END_TRANSMISSION_BLOCK,
CHARACTER_CANCEL, CHARACTER_END_MEDIUM, CHARACTER_SUBSTITUTE, CHARACTER_ESCAPE,
CHARACTER_FILE_SEPARATOR, CHARACTER_GROUP_SEPARATOR, CHARACTER_RECORD_SEPARATOR, CHARACTER_UNIT_SEPARATOR,
CHARACTER_COUNT // Not an actual ASCII table count (128), but for ending special invisible characters.
};
enum {
// I like to align enumerations with 10, 20 or 40 characters per name, and optionally use NAME_ as prefix and NAME_COUNT as last element, look above...
EFFECT_NORMAL, EFFECT_BOLD, EFFECT_ITALIC, EFFECT_UNDERLINE, EFFECT_BLINK, EFFECT_REVERSE,
EFFECT_COUNT
};
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
};
extern void in (void * data, int size); // We'll use these functions later as core standard input/output functions.
extern void out (void * data, int size);
extern void echo (char * data); // Function 'echo' behaves similar to 'out', but it's easier to use because it doesn't require size.
extern void fatal_failure (int condition, char * message); // Some functions fail, and sometimes we want to abort everything, crash and leak memory, we just don't care.
extern void limit (int * value, int minimum, int maximum);
extern void * allocate ( int size); // Core memory management functions with some minimal error checking.
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.
extern int string_compare (char * destination, char * source); // See how nicely they align, right?
extern char * string_copy (char * destination, char * source);
extern char * string_concatenate (char * destination, char * source);
extern char * string_reverse (char * string); // Notice last function, we didn't align ');'...
extern int string_compare_limit (char * destination, char * source, int limit); // These ones too, it's beautiful (in my opinion), tho some consider it useless.
extern char * string_copy_limit (char * destination, char * source, int limit);
extern char * string_concatenate_limit (char * destination, char * source, int limit);
extern char * string_reverse_limit (char * string, int limit); // And we align the last argument in this case, use whatever you prefer.
extern char * string_realign (char * string, int amount, char character); // This is a simple function that realigns a string to right, we'll use it way later ahead.
extern int memory_compare (void * memory, void * source, int length); // We'll cover these functions later, they are more complex.
extern void memory_copy (void * memory, void * source, int length);
// In chapter two, we'll explain ASCII escape sequences, for now, consider this to be some black magic.
extern void terminal_clear (void); // Offset and clear terminal screen output.
extern void terminal_colour (int colour, int effect); // Set terminal character attributes.
extern void terminal_cancel (void); // Reset terminal character attributes.
extern void terminal_show_cursor (int show); // Toggle rendering of terminal cursor.
#endif