The reader should expect to be introduced to main computer science topics, while learns to program in C. Examples are given, to illustrate the point, we'll make a study case in every chapter, showing how to apply and review the theoretical contents. A Q & A section adds extra explanations to some topics. So, it is recommended that the reader doesn't skip any material, for we tried not to stuff the book with platitudes about coding, or computer science footnote knowledge. We want the reader to feel that reading this material will give him meaningful insights, and valuable information, instead of just describing the syntax and how to compile the code, because any one can learn syntactic rules and compile a program to feel good, but few are willing to stay writing inutile code, when s/he can carry about serious computation.
To not be to pedantic, let's just put the famous "hello, world" program now, instead of dedicating a entire section explaining it.
#include <stdio.h>
int main() {
printf("%s", "Hello,World\n");
return 0;
}
I won’t explain it, for it just prints a string to the standard output (stdout file.). Let’s instead, describe the overall structure of a C program.
// ============================================================================
// 1. DOCUMENTATION SECTION
// ============================================================================
/*
* Multi-line comment:
* File: main.c
* Purpose: Complete structural blueprint of a C source file.
*/
// Single-line comment: Explains immediate logic or flags.
// ============================================================================
// 2. PREPROCESSOR DIRECTIVES SECTION
// ============================================================================
// Standard library headers (angle brackets)
#include <stdio.h> // Standard I/O operations (printf, scanf)
#include <stdlib.h> // Memory allocation, process control (malloc, free, exit)
#include <stdbool.h> // Boolean types (bool, true, false)
#include <math.h> // Mathematical operations (e.g., sqrt, pow)
// User-defined headers (double quotes)
// #include "my_header.h"
// Macros and constants
#define MAX_BUFFER 1024
#define SUCCESS 0
// ============================================================================
// 3. GLOBAL DECLARATIONS & TYPE DEFINITIONS
// ============================================================================
// Custom types (structs, unions, enums, typedefs)
typedef struct {
int id;
double value;
} Item;
// Global variables (file-scope or extern; use sparingly)
static int g_counter = 0;
// ============================================================================
// 4. FUNCTION PROTOTYPES (FORWARD DECLARATIONS)
// ============================================================================
// Syntax: <return_type> function_name(<parameter_list>);
// Informs the compiler of functions defined later in the file.
double compute_root(double value);
void log_status(const char *message);
// ============================================================================
// 5. MAIN FUNCTION (PROGRAM ENTRY POINT)
// ============================================================================
/*
* Signature alternatives:
* - int main(void) -> takes no command-line arguments
* - int main(int argc, char *argv[]) -> accepts argument count and vector
*/
int main(int argc, char *argv[])
{
// Local variable declarations and initialization
// Syntax: <data_type> <identifier> = <initial_value>;
double x = 2.0;
// Function call using math library
double root = compute_root(x);
// Formatted output
printf("Square root of %.2f is %.4f\n", x, root);
// Return status code to the operating system:
// 0 / EXIT_SUCCESS indicates normal termination.
// Non-zero / EXIT_FAILURE indicates an error or diagnostic signal.
return SUCCESS;
}
// ============================================================================
// 6. FUNCTION DEFINITIONS (IMPLEMENTATIONS)
// ============================================================================
/*
* Return type can be:
* - Primitive type (int, double, char, etc.)
* - Pointer/Address (<dtype>*)
* - Derived type (struct, union)
* - bool (via <stdbool.h>)
* - void (no return value)
*/
double compute_root(double value)
{
if (value < 0.0) {
return 0.0; // Guard clause / error handling
}
return sqrt(value);
}
void log_status(const char *message)
{
// void functions perform actions without returning data
printf("[LOG]: %s\n", message);
return; // Optional for void functions
}
Chapter 0. is just an outline of C, and its main tools. We will follow each part of the code overall template in chapter 1.
Programming in C can be exhaustive, if you don’t use the appropriate tools to help you out. These tools form a programming, debugging, running and testing, documentation, etc., environments. We will just describe the tools you should have; the actual tool is up to you to decide. I use GNU tools, mostly and Emacs, or nano editors. You can even use the windows notepad if you will, that will work for us. We don’t need any IDE for now, unless you want to use one; they come with many features, unfortunately, IDEs are not always compatible with each other, therefore, a text editor is more portable for your code. Moreover IDEs import many libraries and objects stuffing the code with them, when you really don’t need them for anything useful. The worst trait in a professional programmer is that s/he wants to deliver software as fast as they can, and some are not caring for system’s resources, and algorithmic inefficiency anymore, for they think that the computing powers of modern devices allow them to treat memory, and processing time, as if every problem is tractable, i.e. the algorithm does not quickly get inefficient for an input n of say length |n| = 1000 when the time( number of operations and memory size for a given input) is O(2|n|). We’ll see that later how to measure algorithmic complexity using asymptotic analysis.
As a program is just a text file that is structured according to some syntactic rules, you’ll need a text editor. Any one will do, but if you can find one that is proper to code in, with line numbering, highlighting, tabs, and other aids, it is a nice
Pointers are not that difficult, if you really grasp its concept. Pointers exist in C to provide low-level memory access, in a high-level manner. Memory access is like hacking the operating system, if it did not provide memory segments in which the program cannot access beyond his scope. Why just memory and not the whole CPU control? Well, we have to have in mind that Unix and C code go hand in hand, and some assembly can be called from inside C. The system, in this case Unix, handles the CPU memory, Data Addresses and bus, and file handling in a way that to C everything is just a file, a data address, or a function. Anything else is just remaking the bycycle again, or trying to upgrade its design.
The following code declares a pointer to an integer array, and prints its addresses and values by incrementing the pointer.
#include
int main ()
{
int* pi; // declares a pointer to an integer
int iset[4] = {12,123,1234,12345}; // declares an integer array
int i = 0;
pi = &iset[i]; // assigns the address of the integer array to the pointer
/* sometimes, if the list of itens is short, we don't
need to use a loop to print the values. It optimizes the code.*/
/*
printf("Address: %p, Value: %d\n", pi, *pi);
i++;
pi++;
printf("Address: %p, Value: %d\n", pi, *pi);
i++;
pi++;
printf("Address: %p, Value: %d\n", pi, *pi);
i++;
pi++;
printf("Address: %p, Value: %d\n", pi, *pi);
i++;
pi++;
*/
for (i = 0; i < 4; i++, pi++){
printf("Address: %p, Value: %d\n", pi, *pi);
}
return 0;
}
.LC0:
.string "Address: %p, Value: %d\n"
"main":
push rbp
mov rbp, rsp
sub rsp, 32
mov DWORD PTR [rbp-32], 12
mov DWORD PTR [rbp-28], 123
mov DWORD PTR [rbp-24], 1234
mov DWORD PTR [rbp-20], 12345
mov DWORD PTR [rbp-4], 0
lea rax, [rbp-32]
mov edx, DWORD PTR [rbp-4]
movsxd rdx, edx
sal rdx, 2
add rax, rdx
mov QWORD PTR [rbp-16], rax
mov rax, QWORD PTR [rbp-16]
mov edx, DWORD PTR [rax]
mov rax, QWORD PTR [rbp-16]
mov rsi, rax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call "printf"
add DWORD PTR [rbp-4], 1
add QWORD PTR [rbp-16], 4
mov rax, QWORD PTR [rbp-16]
mov edx, DWORD PTR [rax]
mov rax, QWORD PTR [rbp-16]
mov rsi, rax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call "printf"
add DWORD PTR [rbp-4], 1
add QWORD PTR [rbp-16], 4
mov rax, QWORD PTR [rbp-16]
mov edx, DWORD PTR [rax]
mov rax, QWORD PTR [rbp-16]
mov rsi, rax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call "printf"
add DWORD PTR [rbp-4], 1
add QWORD PTR [rbp-16], 4
mov rax, QWORD PTR [rbp-16]
mov edx, DWORD PTR [rax]
mov rax, QWORD PTR [rbp-16]
mov rsi, rax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call "printf"
add DWORD PTR [rbp-4], 1
add QWORD PTR [rbp-16], 4
mov eax, 0
leave
ret
if you look at the assembly code, it is bigger than with a loop, but
less complex in it operations. Pratically, it just repeats the same
operation four times. In other hand, the loop
.LC0:
.string "Address: %p, Value: %d\n"
"main":
push rbp
mov rbp, rsp
sub rsp, 32
mov DWORD PTR [rbp-32], 12
mov DWORD PTR [rbp-28], 123
mov DWORD PTR [rbp-24], 1234
mov DWORD PTR [rbp-20], 12345
mov DWORD PTR [rbp-12], 0
lea rax, [rbp-32]
mov edx, DWORD PTR [rbp-12]
movsxd rdx, edx
sal rdx, 2
add rax, rdx
mov QWORD PTR [rbp-8], rax
mov DWORD PTR [rbp-12], 0
jmp .L2
.L3:
mov rax, QWORD PTR [rbp-8]
mov edx, DWORD PTR [rax]
mov rax, QWORD PTR [rbp-8]
mov rsi, rax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call "printf"
add DWORD PTR [rbp-12], 1
add QWORD PTR [rbp-8], 4
.L2:
cmp DWORD PTR [rbp-12], 3
jle .L3
mov eax, 0
leave
ret
Not that it uses a jump (jle) to control the loop, and some different
base pointer arithmetics. You can see that the code, when optimized by the
compiler, will run a bit faster than the loop version.
When the loop is too large, it is better to use a function, instead of a loop. We'll see it latter, when dealing with assembly and C.
In chapter 3, we'll see more on pointers, and how to use them to pass data between functions, and how to implement dynamic memory allocation.Functions are fragments of code that receives and return values. They are employed to reduce code mess, and make it easier to understand. They are also used to build modules, and make code more reusable, not just copy and paste, but real modules with an specific purpose.