forked from xolatile/xhartae
82 lines
4.3 KiB
C
82 lines
4.3 KiB
C
|
/*
|
||
|
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_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... */
|
||
|
|
||
|
/*
|
||
|
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.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
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...
|
||
|
*/
|
||
|
|
||
|
void hello_world_0 (void) {
|
||
|
write (STDOUT_FILENO, "Hello world!\n", sizeof ("Hello world!\n"));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
In function 'hello_world_1' we're using function 'puts' this time, which is considered unsafe because it'll fail when input string isn't null terminated. Keep in mind that it also
|
||
|
prints line feed (new line) on the end. Why I call it line feed? Some terminals and text editors distinguish carriage return ('\r' / 13 / CR) and line feed ('\n' / 10 / LF), and
|
||
|
some of them use both, like old teleprinters used to do, where the name 'tty' on UNIX-based operating systems comes from.
|
||
|
*/
|
||
|
|
||
|
void hello_world_1 (void) {
|
||
|
puts ("Hello world!");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Now, in function 'hello_world_2' and function 'hello_world_3', we're using 'printf' function, which is in my opinion important for debugging. It has many variations, so we'll have
|
||
|
a separate chapter only about it. Know that it's a variadic function, which means that it can take more than one input argument, and later we'll learn to make our own variadic
|
||
|
functions as well. It's also a bit unsafe, if you're not careful how you use it, but nothing drastic can happen, most compilers catch those kinds of errors.
|
||
|
*/
|
||
|
|
||
|
void hello_world_2 (void) {
|
||
|
printf ("Hello world!\n");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
It's also worth noting that we're using it's arguments in function 'hello_world_3', function 'printf' will print characters one by one from its' format, until it encounters '%',
|
||
|
if the next character is '%', it prints one '%', if it's 's', then it'll print a string, which we provide in the next argument. When compiler optimizations are turned on, it will
|
||
|
change all sorts of things here, since it's obvious that we're using simple string in this example.
|
||
|
*/
|
||
|
|
||
|
void hello_world_3 (void) {
|
||
|
printf ("%s %s!\n", "Hello", "world");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Lastly, we have a global variable, some people like to call it that, and consider it evil. It's not evil if you know the pros and cons of using them, which causes intellectual
|
||
|
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.
|
||
|
*/
|
||
|
|
||
|
void (* hello_world [4]) (void) = {
|
||
|
hello_world_0,
|
||
|
hello_world_1,
|
||
|
hello_world_2,
|
||
|
hello_world_3
|
||
|
};
|
||
|
|
||
|
#endif
|