/* Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic Xhartae is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation. And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version. 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_HEADER #define CHAPTER_0_HEADER #include // We'll need this header file for malloc, calloc, realloc, free and exit. #include // This one for open and O_ flags. #include // And this one for read, write, close and lseek. /* > 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! 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: 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, 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 * string_0, char * string_1, 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 ); @ 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; static char curses_signal = '\0'; static char * curses_screen = NULL; extern unsigned int positive; extern int negative; extern int * * coordinate_system; static void (* encode_type_0 [TYPE_0_COUNT]) (void) = { NULL }; 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; @ 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) { int32_t main (int32_t argc, char * argv []) { 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. */ 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 { // 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. 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 starting 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. 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. // These will be useful in chapter three, where we'll learn about 'printf' function. It's too complex to cover it at this point. extern char * number_to_string (int number); extern char * format_to_string (int number, int sign, int base, int amount, char character); #endif