318 lines
20 KiB
C
318 lines
20 KiB
C
#ifndef CHAPTER_2_HEADER
|
|
#define CHAPTER_2_HEADER
|
|
|
|
#include <stdio.h> // We need this header file for functions 'puts' and 'printf' in 'hello_world' functions below.
|
|
#include <unistd.h> // And in this header file we have write system call.
|
|
|
|
/*
|
|
In this chapter, you'll learn about:
|
|
|
|
- Curses library (libncurses "clone")
|
|
- More on function declarations
|
|
- Escape sequences
|
|
- General scope
|
|
- Sane program layout
|
|
- Terminal input and output
|
|
|
|
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
|
|
quickly prototyping something out. Function declarations are property of old programming languages, you don't have to use them if you're calling every function after it's defined
|
|
(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. You can see how I declare functions in '.h' files, and define them in '.c' files,
|
|
unless I don't want to make some function external in some '.c' file, I use 'static' there.
|
|
|
|
@C
|
|
// Function declaration: // # // Output: // Input:
|
|
extern void function_0 (void); // 0 // undefined (none) // undefined (none)
|
|
extern float function_1 (double a); // 1 // ieee754_32b // ieee754_64b
|
|
extern int function_2 (int a, int b); // 2 // integer_32b // integer_32b, integer_32b
|
|
static unsigned int function_3 (char * a, int b, char c); // 3 // natural_32b // integer_8b_pointer, integer_32b, integer_8b
|
|
static char * function_4 (char * a); // 4 // integer_8b_pointer // integer_8b_pointer
|
|
static void * function_5 (struct s * a, void * b); // 5 // 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 code 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
|
|
long, while keyword 'double' is same as float, but enlarged to 64 bits or 8 bytes. They are pain to use in some cases because of limited precision.
|
|
2) Functions can have multiple arguments (input variables), but only one return value (output "variable"). In C, you can name the arguments, but not the return value, also keep in
|
|
mind that you should choose descriptive names (in most cases) for function arguments, not name them a, b, c, d like in these examples.
|
|
3) If you really want the compiler to verify that some return value is of type 'unsigned int' aka 'uint32_t', you can do so, I prefer 'int' since all number literals in C are
|
|
integers by default ('int'). We'll use terms like 'int / integer/ int32_t' interchangably, so keep that in mind in future chapters.
|
|
4) Also, type 'char *' can be pointer to (address of) of some 'char' typed variable or array of type 'char'. We'll talk more about arrays in future chapters, but know that we
|
|
could use 'char a []' in functions 3 and 4 as well, it's same. You could use them both in order to know which is array and which is pointer.
|
|
5) Lastly, this function returns pointer to any type, hence compiler won't do type-checking, it's necesary to this sometimes. It also accepts pointer to (previously defined)
|
|
structure 's', and if you used 'typedef' with it, you could just say 's * a', we'll talk more about those.
|
|
|
|
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' in worst case.
|
|
- Don't use 'const' keyword at all, that's my honest advice, it won't catch that many potential bugs in C, it'll just 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...
|
|
- You can use '[]' in function declarations, but it doesn't mean much since array is passed as pointer to first element in C (array decay), so '*' is enough.
|
|
- 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, I'll mention that a lot.
|
|
- Last one is tricky, you should name function agruments in function declarations, but some linters will warn you not to do it, since some compiler don't check them.
|
|
|
|
Very soon, you'll be able to write your own small C programs, so prepare for it. Also, the most important chemical element for organisms is, you guessed it, C (carbon). It's
|
|
the same in programming too, C is the most important language that a good programmer should know. Other languages are just extra. Now:
|
|
|
|
This is probably the first program new programmers write in language they're learning. It simply prints text to standard output. As C is very old programming language, you have a
|
|
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.
|
|
*/
|
|
|
|
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'.
|
|
|
|
/*
|
|
Alright, we'll never use those 4 functions, but now you know how to declare any function, it's time for more serious examples. Below you'll see some scary looking cursed functions
|
|
that interact with the terminal in which we're executing this program. We'll change the terminal from default (sane) mode to raw (insane) mode. That means the characters we press
|
|
on our keyboard won't be printed in terminal, like usual. That'll allow us to make cursed C program that'll scare away all GUI fanatics. In the ancient times, when people used
|
|
teletypewriters, terminal was obscure magic. Nowdays, parents scare their children into obedience by showing them terminals. Like, if you don't behave, I'll edit X11 scripts and
|
|
you won't be able to see icons, you won't pass the Login text on your screen, you'll never open your web browser again (lynx rules).
|
|
*/
|
|
|
|
#include "chapter_0.h" // We use functions declared in these two header files, so in order to make them visible, we included those files here.
|
|
#include "chapter_1.h"
|
|
|
|
#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)
|
|
|
|
#define SIGNAL_CONTROL (0X1000000) // We're also defining some signal masks, they'll be used later to mark which key combination is pressed.
|
|
#define SIGNAL_SHIFT (0X2000000)
|
|
#define SIGNAL_ALTERNATE (0X4000000)
|
|
#define SIGNAL_SYSTEM (0X8000000)
|
|
|
|
enum {
|
|
// This enumeration will be used for signal processing, I put some special cases on their own line.
|
|
SIGNAL_NONE,
|
|
SIGNAL_ANY,
|
|
SIGNAL_A, SIGNAL_B, SIGNAL_C, SIGNAL_D, SIGNAL_E, SIGNAL_F, SIGNAL_G, SIGNAL_H,
|
|
SIGNAL_I, SIGNAL_J, SIGNAL_K, SIGNAL_L, SIGNAL_M, SIGNAL_N, SIGNAL_O, SIGNAL_P,
|
|
SIGNAL_Q, SIGNAL_R, SIGNAL_S, SIGNAL_T, SIGNAL_U, SIGNAL_V, SIGNAL_W, SIGNAL_X,
|
|
SIGNAL_Y, SIGNAL_Z, SIGNAL_0, SIGNAL_1, SIGNAL_2, SIGNAL_3, SIGNAL_4, SIGNAL_5,
|
|
SIGNAL_6, SIGNAL_7, SIGNAL_8, SIGNAL_9, SIGNAL_ESCAPE, SIGNAL_TABULATOR, SIGNAL_RETURN, SIGNAL_NEW_LINE,
|
|
SIGNAL_SLASH, SIGNAL_BACKSLASH, SIGNAL_QUOTE, SIGNAL_BACKQUOTE, SIGNAL_SPACE, SIGNAL_BACKSPACE, SIGNAL_DOT, SIGNAL_COMMA,
|
|
SIGNAL_QUOTATION, SIGNAL_CAPS_LOCK, SIGNAL_L_BRACKET, SIGNAL_R_BRACKET, SIGNAL_MINUS, SIGNAL_EQUAL,
|
|
SIGNAL_COUNT
|
|
};
|
|
|
|
/*
|
|
These below are external variables, they can be accessed and modified in any file where they're (re)declared. We're compiling C source files (.c) separately, so the compiler will
|
|
output object files (.o), and then we can link them together (most compilers are also linkers, this is nothing out of ordinary) into final executable. We can, for example, use
|
|
functions declared in "chapter_2.h" and defined in "chapter_2.c" in completely separate file "chapter_3.c", if we have included it with '#include "chapter_2.h". Keep in mind that
|
|
header files can be included recursively, that's why we use those header guards, '#ifndef SOMETHING', '#define SOMETHING' and '#endif' at the end. That way, they'll be included
|
|
only once, so compiler won't be confused and spit out errors. You can see how I use those chapters to create new programs, and all that is in 'program/' folder. You can also just
|
|
open some 'compile.sh' file or 'makefile' and see how I compile and link.
|
|
|
|
Now, lets talk about ASCII escape sequences. Most terminals support some special combination of characters, that aren't printed like you'd normally expect. We call them escape
|
|
sequences because they all start with character literal '\033', '\x1b' or '\e', which is same as decimal 27, and in our character enumeration 'CHARACTER_ESCAPE'. They are somewhat
|
|
standardized way of interacting with your terminal. I'll use '\033' here because it does well with C strings and it's the most widely supported. Also, we'll use formatting of
|
|
'printf' function from <stdio.h>, which we'll need to cover later in more details.
|
|
|
|
"\033[H": Set cursor position to upper left corner (position 1, 1).
|
|
"\033[%i;%iH": Set cursor position at Y and X coordinates, ranging from 1 to N (I simply limit N as 1000, and they can be prefixed with '0's).
|
|
"\033[6n": Get current cursor position, this will output response string defined just below.
|
|
"\033[%i;%iR": Response is similar to 'set-cursor-position' string, except it's 'R' instead of 'H', so you'll need to parse it.
|
|
"\033[2K": Clear an entire line.
|
|
"\033[2J": Clear an entire screen.
|
|
"\033[c": Reset terminal to initial state, we won't use this string, but 'tcsetattr' function.
|
|
"\033[%dA": Move cursor N characters up, if you don't provide N, it's assumed to be equal to 1.
|
|
"\033[%dB": Move cursor N characters down, if you don't provide N, it's assumed to be equal to 1.
|
|
"\033[%dC": Move cursor N characters right, if you don't provide N, it's assumed to be equal to 1.
|
|
"\033[%dD": Move cursor N characters left, if you don't provide N, it's assumed to be equal to 1. Multiple cursors edited this file...
|
|
"\033[%d;3%dm": Set character attributes (effect and colour) to types described below, first is effect, second is colour.
|
|
"\033[0m": Reset character attributes to default state.
|
|
|
|
Effects: Colours:
|
|
- Normal: 0 - Grey: 0
|
|
- Bold: 1 - Red: 1
|
|
- Italic: 3 - Green: 2
|
|
- Underline: 4 - Yellow: 3
|
|
- Blink: 5 - Blue: 4
|
|
- Reverse: 7 - Pink: 5
|
|
- Cyan: 6
|
|
- White: 7
|
|
|
|
Instead of hardcoding values yourself and remembering those, you can just use enumeration identifiers, like, COLOUR_BLUE and EFFECT_BOLD.
|
|
*/
|
|
|
|
extern int curses_character; // Literal character (integer) that user has pressed, some of them are more than single byte, like arrow keys defined above.
|
|
extern int curses_signal; // Converted value of 'curses_character' into values of macros and enumeration values named as 'SIGNAL_*'.
|
|
extern int curses_screen_width; // Width of terminal window in which we're rendering.
|
|
extern int curses_screen_height; // Height of terminal window in which we're rendering.
|
|
extern int curses_active; // As long as there's no termination signal, this variable will be different from zero. We use it to exit the program.
|
|
|
|
/*
|
|
Now, lets talk about scope. Roughly speaking, every variable, constant, function, enumeration, structure, union, definition, declaration, file, anything really has some scope,
|
|
where it is known to the compiler, visible. If, for example you're using 'printf' function from <stdio.h> header file, and you didn't write "#include <stdio.h>" before that line
|
|
in which you're using it (and outside of function definition, that's K&R thing, I'll explain later), compiler will get scared and confused, and it'll just drop dead. You're left
|
|
with no executable, compiler spat out some error messages, you don't know what they mean, it's over.
|
|
|
|
So, if you have something like this:
|
|
|
|
@C
|
|
int F (int A) {
|
|
int V = randomize (0, A);
|
|
|
|
return (A + V);
|
|
}
|
|
@
|
|
|
|
Variable 'V' is local to function 'F', and to no other function, you can only use variable 'V' inside function 'F', but you can name other local variable as 'V' too, no problems.
|
|
|
|
Inside file named "a.c", for example:
|
|
|
|
@C
|
|
static int X = 100;
|
|
static int Y = 200;
|
|
static int Z = 300;
|
|
@
|
|
|
|
Variables 'X', 'Y' and 'Z' are only visible in file "a.c", and no other file. You can modify them inside any function in that file, but don't name any local variables as that.
|
|
|
|
You want to use global variables? You should have declarations in header file, and definitions in source file:
|
|
|
|
@C
|
|
// a.h
|
|
extern char * A;
|
|
extern char * B;
|
|
extern char * C;
|
|
@
|
|
|
|
@C
|
|
// a.c
|
|
#include "path/to/file/a.h"
|
|
// ...
|
|
char * A = "Heyo";
|
|
char * B = "Cyaa";
|
|
char * C = "Meme";
|
|
@
|
|
|
|
In this case, you can modify variables 'A', 'B' and 'C' in any file that has included header file "a.h", not only "a.c", but you must define them only once, as shown here.
|
|
|
|
When you forget to include some header file, provide a variable, constant, function declaration or definition, compiler will warn you about it, because it can guess in simple
|
|
cases where that function is declared, but it can't (shouldn't) scan your entire SSD or HDD to find it, because it would take too long and maybe you just mistyped the name of it.
|
|
If you forget to write "#include <unistd.h>", and use write or read "functions", the compiler will kindly tell you to include it, but it would assume you want to use that very
|
|
functions, and it will compile it anyway, giving you your executable in hope it's correct. It can't do that always...
|
|
|
|
In K&R C, aka old C, it was something like this (don't mind the stupid example):
|
|
|
|
@C
|
|
// K&R C (with a lot of implcit state) // Sadly new C standards (verbose and "safe")
|
|
putchar (c) { extern int putint (int file, int data, int size);
|
|
extern putint;
|
|
static int putchar (int c) {
|
|
putint (1, c, 1); return (putint (1, c, 1));
|
|
} }
|
|
@
|
|
|
|
Essentially, you'll get the feeling for it after few compiler warnings, don't be afraid to make mistakes that compiler can catch.
|
|
|
|
About my preffered program layout, here's what I think about it:
|
|
|
|
@C
|
|
// License notice if you want.
|
|
|
|
// Header guards or definitions.
|
|
#ifndef BLA_BLA_HEADER
|
|
#define BLA_BLA_HEADER
|
|
|
|
#define _DEFAULT_SOURCE
|
|
|
|
// Standard library C header files.
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
// User provided C source or header files.
|
|
#include "heyo.h"
|
|
#include "cyaa.c"
|
|
|
|
// Macros that you really want to have.
|
|
#define UNUSED(x) (void) x
|
|
|
|
// Internal function declarations then variable definitions.
|
|
static void uberheyo (void);
|
|
// Or:
|
|
static void ubercyaa (void) {
|
|
// ...
|
|
}
|
|
|
|
static int h = 0;
|
|
static int c = 1;
|
|
|
|
// External function then variable declarations.
|
|
extern void heyo (void);
|
|
extern void cyaa (void);
|
|
|
|
extern int H;
|
|
extern int c;
|
|
|
|
// Internal function definition, if not defined above...
|
|
void uberheyo (void) {
|
|
// ...
|
|
}
|
|
|
|
// External function then variable definition, if not in included C source file.
|
|
void heyo (void) {
|
|
// ...
|
|
}
|
|
|
|
void cyaa (void) {
|
|
// ...
|
|
}
|
|
|
|
int H = 0;
|
|
int C = 1;
|
|
|
|
// Main function.
|
|
int main (void) {
|
|
// ...
|
|
}
|
|
// Or:
|
|
int main (int argc, char * * argv) {
|
|
// ...
|
|
}
|
|
|
|
// You can also define internal or external functions here if you want to.
|
|
|
|
// End of header guards.
|
|
#endif
|
|
@
|
|
|
|
Find one style, and try to be consistent about it, I have to show-case more example of how things look like, so I use more approaches...
|
|
*/
|
|
|
|
extern void curses_configure (void); // I like using the approach of "just do it" instead of "begin" and "end", due to that we'll see more internal functions and variables.
|
|
extern void curses_synchronize (void); // This function needs to be explained in more details, that'll be done in file "chapter_2.c".
|
|
|
|
extern void curses_bind (int signal, void (* action) (void)); // Binding specific function pointer to some key, so each time key is pressed, function is executed.
|
|
extern void curses_unbind (int signal); // Unbinding any function that was bound to value of 'signal'.
|
|
|
|
extern void curses_render_cursor (int x, int y); // Render terminal cursor at position X and Y.
|
|
|
|
extern void curses_render_character (char character, int colour, int effect, int x, int y); // Render single character at position X and Y.
|
|
extern void curses_render_background (char character, int colour, int effect); // Render entire buffer with the same character.
|
|
|
|
extern void curses_render_rectangle_fill (char character, int colour, int effect, int x, int y, int width, int height); // Guess what these functions do...?
|
|
extern void curses_render_rectangle_line (char character, int colour, int effect, int x, int y, int width, int height);
|
|
|
|
// Remember that in chapter zero, I've separated 'string_*' and 'string_*_limit' functions. Now, there's always more ways to logically organize your code, for example, as below:
|
|
extern int curses_render_string (char * string, int colour, int effect, int x, int y);
|
|
extern int curses_render_number (int number, int colour, int effect, int x, int y);
|
|
extern int curses_render_string_limit (char * string, int limit, int colour, int effect, int x, int y);
|
|
extern int curses_render_number_limit (int number, int limit, int colour, int effect, int x, int y);
|
|
// I really hope that you already know what these functions do just by looking at the names of the arguments...
|
|
|
|
#endif
|