C Fundamentals

Introduction to the most powerful low-level programming language - C.
This article sums up the basics of C and is intended for people who already know something about programming and want to start learning C, for people who want to refresh their memories of this programming language and for tech students.
Contents:
cover

Why C? 

  • C is an universal and powerful low-level programming language
  • UNIX system, Linux and Windows Kernel are written in C
  • over 80% devices, embedded systems and IoT are programmed in C
  • C is known as the father of almost any modern programming language
  • many modern languages (PHP, Python, ...) are written in C and many other (C++, C#, Java, ...) are strongly influenced by C
  • C is one of the fastest and most powerful languages
  • most technical universities in the world teaches C
  • once you fully understand C, you will understand how computers works and you will be able to learn any other programming language in no time    

Before we start

 It's important to mention some pre-preparations and tools needed to write basic C programs, such as:     

  • text editor or IDE (recommended are Atom, Visual Studio Code or Code::Blocks), if you work on Windows platform, then Microsoft Visual Studio, Dev C/C++ or Code::Blocks.
  • compiler (gcc/g++, if you work on UNIX or Linux, these compilers should be already built-in), if you work on Windows platform, mentioned IDE should already contain some C compiler. If not, you can download gcc from here
    To compile our program just run gcc main.c in terminal
  • we will be using only Standard C library and Linux system in our examples, and so please note that we won't be using functions from conio.h nor any other Windows library if you are used to them

Hello World break down

After creating a C file (main.c) in our text editor (IDE), the very basic structure of a C code would look like this:      

#include <stdio.h>

int main ( void ) {

    printf("Hello world!\n");

    return 0;
}

Now let's break down each part of the code (non beginners are to skip this part):     

#include <stdio.h> // Header file - standard input/output

/**
 * Main function
 * This function is the first one to be called after executing our C program
 * @parameter is either void (can be left blank) or contains command line arguments
 * in that case it would look like:
 * int main(int argc, char *argv[])
**/
int main ( void ) {

    printf("Hello world!\n"); // Function (stored in stdio.h) to print a string into standard output, \n prints newline

    return 0; // int main() is a function, so it has to return an integer value
}

Cheat sheet

Cheat sheet, in other words, everything you need to know.

Data types

data type   |   description         |   range
-------------------------------------------
char        |   ASCII character     |   (signed) -128 to 127 or (unsigned) 0 to 255
short       |   2 bytes integer     |   (signed) -32 768 to 32 767 or (unsigned) 0 to 65 5635
int         |   2 or 4 bytes integer|   short or long (depends on compiler)
long        |   4 bytes integer     |   (signed) -2 174 483 648 to 2 147 483 647 or (unsigned) 0 to 4 294 967 295
long long   |   8 bytes integer     |   (signed) -2^63+1 to +2^63-1 (unsigned) 0 to +2^64-1
float       |   floating-point (4B) |   1.2*10^-38 to 3.4*10^+38 (6 decimal places)
double      |   floating-point (8B) |   2.3*10^-308 to 1.7*10^+308 (15 decimal places)
long double |   floating-point (10B)|   3.4*10^-4932 to 1.1*10^+4932 (19 decimal places)

Note: yes, C doesn't have string type. To create a string in C, you have to create an array of characters char*.

Also, short is the same as short int and is the same as signed short int. The same goes for long. If not defined, the type will be automatically signed.

Arithmetic operators

operator	|	description				|	example
------------------------------------------------------------
+			|	addition				|	i = 5 + 3; (i is 8)
-			|	subtraction				|	i = 5 - 3; (i is 2)
*			|	multiplication			|	i = 5 * 3; (i is 15)
/			|	division				|	i = 9 / 3; (i is 3)
%			|	modulation division		|	i = 5 % 2; (i is 1)
++			|	increment (i = i + 1)	| 	i = 0; i++; (i is 1)
--			|	decrement (i = i - 1)	| 	i = 5; i--; (i is 4)

Operators can be also used in a short way:

i += 5; // the same as i = i + 5;
i /= 5; // the same as i = i / 5;
...

Also, be careful when dividing. The result can be either integer or real number depending on data types you divide:

data types		|	result	|	example
-----------------------------------------------------
int/int			| 	int		|	int i = 10; int j = 3; 		i/j = 3
int/double 		|	real	|	int i = 10; double j = 3; 	i/j = 3.333...
double/double 	|	real	|	double i = 10; double j = 3; i/j = 3.333...
double/int 		|	real	|	double i = 10; int j = 3;	i/j = 3.333...

increment/decrement:

operator	|	description				|	example	
----------------------------------------------------------------------------
++i			|	increment before usage	|	i = 0; j = ++i; (i = 1 and j = 1)
i++			| 	increment after usage	|	i = 0; j = i++; (i = 1 and j = 0)
--i			|	decrement before usage	|	i = 5; j = --i; (i = 4 and j = 4)
i--			| 	decrement after usage	|	i = 5; j = i--; (i = 4 and j = 5)

Bitwise operators

operator	|	description				|	example
----------------------------------------------------------------------
<<			|	bit shift to the left	|	i = 5; i <<= 1; (i is 10)
>>			|	bit shift to the right	|	i = 10; i >>= 1; (i is 5)
&			|	bitwise AND				|	i = 1 & 2;	(i is 0) NOTE: & is different from && which is logical AND
|			|	bitwise OR				|	i = 1 | 2; 	(i is 3) NOTE: | is different from || which is logical OR
~			|	bitwise complement		|	i = ~5; (i is -6)

To work with bitwise operators, you need to understand binary first. 

Let's write a calculator

The best way to start is to write a simple calculator, don't you agree?

It will help us to learn working with the arithmetic operators and data types mentioned in the Cheat Sheets.      

int x = 50; 
int y = 30;

printf("The sum of %d and %d is %d\n", x, y, (x + y));

printf("The difference of %d and %d is %d\n", x, y, (x - y));

printf("The product of %d and %d is %d\n", x, y, (x * y));

if (y == 0) { // if second number is 0, we print error
    printf("Division by zero!\n");
}
else { // else, we print result as floating-point
    printf("The quotient of %d and %d is %lf\n", x, y, ((double)x / y)); // casted x to double for real number result
}

This is a simple "calculator" that will be improved later and the output would look like this:   

The sum of 50 and 30 is 80
The difference of 50 and 30 is 20
The product of 50 and 30 is 1500
The quotient of 50 and 30 is 1.666667

Looks confusing? Let me explain..    

The first argument of our printf() function is the string to be printed, however we can also print the values of our variables by inserting format specifier (%d) in the string and putting our wanted variable as the next argument - in this case an integer. In the case of division, I used %lf instead of %d, because I wanted to print the result as a floating-point (double in this case) whereas x and y are both integers. For the complete list of the format specifiers, please have a look at the following table:

specifier   |   description             |   data type           |   example
-------------------------------------------------------------------------------
%c          |   ASCII character         |   char                |   'a'
%s          |   string                  |   char*               |   "Hello, world!"
%hi         |   short signed int        |   short               |   -5000
%hu         |   short unsigned int      |   unsigned short      |   64000
%i or %d    |   signed integer          |   int                 |   5000000
%u          |   unsigned int            |   unsigned int        |   5000000
%li         |   long signed int         |   long                |   -2000000000
%lu         |   long unsigned int       |   unsigned long       |   4000000000
%lli        |   long long signed int    |   long long           |   -5000000000
%llu        |   long long unsigned int  |   unsigned long long  |   5000000000
%f or %F    |   digital notation        |   float               |   1.200000
%g or %G    |   scientifit notation     |   float               |   1.2
%e or %E    |   scientifit notation     |   float               |   1.200000e+00
%a or %A    |   scientifit notation     |   float               |   0x1.333334p+0
%lf or %lF  |   digital notation        |   double              |   1.200000
%lg or %lG  |   scientifit notation     |   double              |   1.2
%le or %lE  |   scientifit notation     |   double              |   1.200000e+00
%la or %lA  |   scientifit notation     |   double              |   0x1.3333333333333p+0
%x or %X    |   hexadecimal             |                       |   1A2C
%o          |   octal                   |                       |   065
%p          |   pointer address         |                       |   0x145484

To print a special character such as % in our output, we just need to backslash it:

printf("\n"); // prints newline
printf("\\n"); // prints "\n"
printf("%"); // throws warning, prints nothing
printf("\%"); // prints "%"
printf("\\"); // prints "\"

Control statements

These control statements are very similar or the same in any other programming language so if you are not a beginner, you are to skip this part as well. The statements are:

if | else if | else
switch
for
while
do while

With these statements we can control the flow of the program and execute certain part of it if a certain condition is met etc..

Let's demonstrate it on a quick example:

#include <stdio.h>

int main ( void ) {

    /* Some variable declarations */
    int temperature = 30; // outside temperature in °C
    double sunny = 0.3; // 0 - overcast, 1 - very sunny
    typedef enum { YES, MAYBE, NO } myBool; // C doesn't have boolean data type, so let's create our own
    myBool raining = NO; // is it raining?
    myBool willGoOutside = MAYBE; // will I go outside?
    myBool didIGetCandy = NO; // did I get a candy?

    /* IF-ELSE statements */
    if (temperature < 10) {
        willGoOutside = NO;
    } 
    else if (temperature >= 20 && temperature <= 30) {
        if (sunny < 0.5 || raining == NO) {
            willGoOutside = NO;
        }
        else {
            willGoOutside = YES;
        }
    }
    else {
        willGoOutside = MAYBE;
    }

    /* SWITCH statement */
    switch (willGoOutside) {
        case YES: printf("I will go outside.\n"); break;
        case MAYBE: printf("I don't know if I will go outside or not yet\n"); break;
        case NO: printf("I am not going outside.\n"); break;
        default: printf("Well... this won't happen."); // No need to put break; in last case
    }

    /* WHILE loop */
    while (didIGetCandy == NO) { // Endless loop
        printf("GIVE ME CANDY!\n");
    }

    /* DO-WHILE loop */
    do {
        printf("GIVE ME CANDY!\n");
    } while (didIGetCandy == NO); // This will print GIVE ME CANDY once even if didIGetCandy was YES

    /* FOR loop */
    for (int i = 0; i < 50; i++) { // Executes 50 times
        printf("Thank you!\n");
    }    

    return 0;
}

There is no real sense in this code so don't try to think about it much. It's just to demonstrate the usage of these statements.

If you don't know what is enum as I didn't mention it in the data types cheat sheet, enum is a custom data type that can contains only given constant values. In this case, I created a data type called myBool consisting only of values YES, MAYBE and NO. The keyword typedef is so I don't have to write they keyword enum before every myBool declaration. In other words, enum is our own defined data type.

Input

Reading and working with user's input.

getchar()

The easiest way of reading user's input is with getchar() function that reads the first character from standard input:

char c;

printf("Enter character: ");
c = getchar();
printf("Your character is %c\n", c);

results:

Enter character: a
Your character is a
Enter character: hello
Your character is h

As you can see, this function only reads the first and only one character of the input. To read the whole line, we will need to run this function in a loop until it meets the <Enter> key:

char c;

printf("Enter text: ");

while (c != '\n') { // \n represents newline
    c = getchar();
    printf("%c", c);
}

output of the above code:

Enter text: hello world!
hello world!

Now, let's improve our calculator with this function!

#include <stdio.h>

int main ( void ) {
    
    char c, op;
    int x = 0;
    int y = 0;

    printf("Enter first number: ");
    while (c != '\n') { // while reading the whole line
        c = getchar();
        if (c != '\n') { // if the character isn't a newline
            x = x * 10 + (c - '0'); // (c - '0') converts ASCII character to integer
            // this equation creates a single number from many inputted numbers
            // for example [1], [5] and [0]: 
            // 0 * 10 + 1 = 1
            // 1 * 10 + 5 = 15
            // 15 * 10 + 0 = 150
        }
    }

    printf("Enter operand (+, -, *, /): ");
    op = getchar(); // reads operand
    if (op != '+' && op != '-' && op != '*' && op != '/') { // If operand is not valid
        printf("Error: wrong operand\n");
        return 1; // exit program
    }
    getchar(); // get (and 'delete') the newline after the operand

    printf("Enter second number: ");
    do { // Using do-while here because c is still a newline
        c = getchar();
        if (c != '\n') {
            y = y * 10 + (c - '0');
        }
    } while (c != '\n');

    // Printing results
    switch (op) {
        case '+': printf("The sum of %d and %d is %d\n", x, y, (x + y));                break;
        case '-': printf("The difference between %d and %d is %d\n", x, y, (x - y));    break;
        case '*': printf("The product of %d and %d is %d\n", x, y, (x * y));            break;
        case '/': 
            if (y == 0) {
                printf("Division by zero!\n");
                break;
            } else {
                printf("The quotient of %d and %d is %lf\n", x, y, ((double)x / y)); 
                break;
            }
    }

    return 0;
}

Even though this code is not perfect (number validation etc..), it still looks promising and should work fine. Let's test it out:

Enter first number: 5 
Enter operand (+, -, *, /): +
Enter second number: 20
The sum of 5 and 20 is 25

Enter first number: 1243
Enter operand (+, -, *, /): -
Enter second number: 200
The difference of 1243 and 200 is 1043

Enter first number: 3
Enter operand (+, -, *, /): *
Enter second number: 100
The product of 3 and 100 is 300

Enter first number: 150
Enter operand (+, -, *, /): /
Enter second number: 6
The quotient of 150 and 6 is 25.000000

Enter first number: 13
Enter operand (+, -, *, /): /
Enter second number: 0
Division by zero!

Enter first number: 10
Enter operand (+, -, *, /): /
Enter second number: 3
The quotient of 10 and 3 is 3.333333

Enter first number: 10
Enter operand (+, -, *, /): what       
Error: wrong operand

Yes, it works just fine.

scanf()

Another way of reading user's input is with the scanf() function, which also allows us to read the input in a formatted way using the format specifiers.

int i;
scanf("%d", &i);

First argument of the scanf() function defines the format of the input we ant to read and following arguments are the addresses where to store the input (that's why it's important to use &i instead of just i). We can easily rewrite our simple calculator using this function:

#include <stdio.h>

int main ( void ) {
    
    char c;
    int x, y;

    scanf("%d %c %d", &x, &c, &y); // first number - operand - second number

    switch (c) {
        case '+': printf("The sum of %d and %d is %d\n", x, y, (x + y)); break;
        case '-': printf("The difference of %d and %d is %d\n", x, y, (x - y)); break;
        case '*': printf("The product of %d and %d is %d\n", x, y, (x * y)); break;
        case '/': 
            if (y == 0) printf("Division by zero!\n");
            else printf("The quotient of %d and %d is %lf\n", x, y, ((double)x / y));
            break;
        default: printf("Wrong operand.\n"); break;
    }

    return 0;
}

Looks better than the first one right? Let's check out the results:

5+20
The sum of 5 and 20 is 25

1243 - 200
The difference of 1243 and 200 is 1043

3*100
The product of 3 and 100 is 300

150/6
The quotient of 150 and 6 is 25.000000

13/0
Division by zero!

10/3
The quotient of 10 and 3 is 3.333333

10 what
Wrong operand!

In order to make sure the inputted text format is valid to the one we wanted, it's important to know that this function actually returns the number of successfully filled items in the argument list. In this case, we should check for 3 as we wanted to fill 3 variables in our scanf() function:

if (scanf("%d %c %d", &x, &c, &y) != 3) {
    printf("Invalid format.\n");
    return -1;
}

result:

hello+59
Invalid format.

You may ask why to use getchar() if we can use scanf() which seems to be more elegant and better in many ways. Why did we even bother with the getchar() example?

Well, both of these functions are different and can have a better use in different cases than the other. It's always up to the programmer to think of the best solution and choose the best methods to approach it. Remember that no information is useless. It's always about how you will use it and work with it.

 

Working with files

Using files for testing

Before we get into reading and writing into a file in C, let me mention something about working with files for C programming first. To know how to work with files in C can be very handy not only for certain tasks that requires it, like generating files, writing contents into a file or create temporary files... it can also be very useful for testing the actual program. For example, our calculator from my previous article, when we run the program to see how it works, we test all possible input cases we can think of - addition, subtraction, multiplication and division, giving it some random numbers to check if it works correctly. Also testing how it handles certain cases like dividing by zero and invalid inputs... Now imagine doing this kind of "testing" every time we do minor changes to the code and run the program. That would be time consuming as hell. One of the programmer's job is to think of the most effective solution to any problem. And so, we can just write these test cases into some test files and pass those files to our program as the input:

// input-test.txt (testing data)
5+20
1243 - 200
3*100
150/6
13/0
10/3
10 what

// output-test.txt (expected output of the program)
The sum of 5 and 20 is 25
The difference of 1243 and 200 is 1043
The product of 3 and 100 is 300
The quotient of 150 and 6 is 25.000000
Division by zero!
The quotient of 10 and 3 is 3.333333
Wrong operand!

(In Bash (UNIX shell)), to pass the input-test.txt file to our program as input:

./a.out < input-test.txt

And to test, whether the output of our program is same of different from the expected one in output-test.txt, we can use diff program:

diff output.txt <(./a.out < test.txt)

If we want to write all the program's output into a file, we just do:

./a.out > output.txt

 Okay, I digressed a bit. Back to the main topic. 

Reading and writing into files

The basic way to work with files in C is to use the data type FILE * (the * stands for pointer, for more information about pointers scroll down).
Opening a file:

FILE * fw = fopen("file1.txt", "w"); // opens file1.txt for writing
FILE * fr = fopen("file2.txt", "r"); // opens file2.txt for reading

First argument of the function fopen() defines the file to be opened. We can open the file for either reading or writing, which is defined in the second argument ("w" for writing, "r" for reading).

Basic functions to work with the file:

char c = getc(fr);    // reading a character from the file, similar to getchar()
putc(c, fw);          // writing a character to the file, similar to putchar()
fscanf(fr, "%d", &i); // formated reading from the file, similar to scanf()
fprintf(fw, "%d", i); // formated writing to the file, similar to printf()

It's important to always close the file after we finish our work with it.

fclose(fr);

Simple example of a program, that reads the first character from file DATA.txt and writes that character into RESULT.txt as equivalent ASCII number.

#include <stdio.h>

int main() {
    FILE *fr, *fw;
    char c;

    fr = fopen("DATA.txt", "r");
    fw = fopen("RESULT.txt", "w");

    c = getc(fr);
    fprintf(fw, "%d", c);

    fclose(fr);
    fclose(fw);

    return 0;
}

It's good to mention the end-of-file indicator (EOF or feof()).

// reads the whole content of file in fr
// and writes this content into a file in fw
// feof() stands for end-of-file indicator
// could be also replaced with EOF constant:
// while ((c = getc(fr)) != EOF) {...}
while (c = getc(fr), !feof(fr)) { 
    putc(c, fw);
}

Also keep in mind that when you open or close a file you were working with, it's not always guaranteed that this action will succeed. The file you are trying to open might not exist or you don't have the permission to etc... there are many various reasons why it wouldn't have succeeded. So it's a good habit to always check if the file was successfully opened and closed and proceed depending on it's results.

// if fopen() fails, it returns NULL
if ((fr = fopen("DATA.txt", "r")) == NULL) {
    printf("File DATA.txt failed to open.\n");
    return 1;
}

...

// if fclose() fails, it returns EOF
if (fclose(fr) == EOF) {
    printf("File couldn't be closed.");
    return 1;
}

Pointers

Pointers are the core, the heart and soul of C. Unless you understand pointers and know how to work with them, you can never claim yourself as being a C programmer or not even being someone with the actual knowledge of C (anyone can write hello world).

So, what is a pointer? To put it simply, the difference between a normal variable and pointer is that a variable stores some kind of a value whereas a pointer stores the memory address of a certain variable (every variable has allocated space in memory, this space has an address). To be more concrete, a pointer points to the first address of the allocated memory of a certain variable. It's easier to understand from examples.

#include <stdio.h>

int main() {

    int *p_i; // declaration of pointer p_i that will point to an integer
    int i = 10; // declaration of integer i

    p_i = &i; // assigning the address of i (not the value) to pointer p_i (for example address 0x1)

    i = 15; // change the value of i

    printf("%d\n", *p_i); // prints 15 (the value stored in address 0x1 is 15)
    printf("%p\n", p_i); // prints the address of i (0x1 for example)
    printf("%p\n", &i); // equivalent to the printf() above, prints the address of i

    *p_i = 30; // changing the value in 0x1 to 30

    printf("%d\n", *p_i); // prints 30 (the value stored in address 0x1)
    printf("%d\n", i); // prints 30 (i is stored in 0x1 of which value we changed)

    return 0;
}

Note: When printing an address using the %p specifier, we should correctly cast the argument to (void*)

printf("%p\n", (void*)&i);

The reason why pointers are the heart of C is because we use them everywhere. From working with arrays, functions, strings to making the program faster and more efficient with the right use of pointers.

Arrays

When working with arrays, we have to keep in mind that array indexes are counted from 0 (there are many cringy jokes on the internet about this).

int x[10];    // defines an array x of 10 integers
x[0] = 5;     // first element is at index 0
x[9] = 150;   // last element is at index 9

When we create an array, we actually create a pointer that points to the address of the first element of the allocated array. In other words, pointers are actually arrays with undefined sizes (the size can be allocated dynamically, see Dynamic Arrays bellow).

Note: Not really, but it's fine to think of it that way when working with arrays.

#include <stdio.h>

int main() {
    
    int x[2];
    int *p_x;

    p_x = x; // p_x and x points exactly to the same address

    p_x[0] = 150;
    x[1] = 13;

    /* These callings are equivalent (prints 150) */
    printf("%d\n", x[0]);
    printf("%d\n", *x);
    printf("%d\n", p_x[0]);
    printf("%d\n", *p_x);

    /* These callings are equivalent (prints 13) */
    printf("%d\n", x[1]);
    printf("%d\n", *(x+1));
    printf("%d\n", p_x[1]);
    printf("%d\n", *(p_x+1));

    return 0;
}

Static Arrays

Static arrays, or statically allocated arrays are the classic arrays where we define their sizes in the code and work with them as they are.

#include <stdio.h>
#define MAX 150

int main() {
    
    int x[10];      // defines array x of 10 elements
    int y[MAX];     // defines array y of 150 elements
    int z[5] = { 5, 3, 8, 4, 6 }; // explicit declaration

    // filling all elements of array x with value 0
    for (int i = 0; i < 10; i++) {
        x[i] = 0;
    }

    // filling all elements of array y with user input
    for (int i = 0; i < MAX; i++) {
        scanf("%d", &y[i]);
    }

    // printing all elements of array y
    for (int i = 0; i < MAX; i++) {
        printf("%d ", y[i]);
    }

    // prints 5 3 8 4 6
    for (int i = 0; i < 5; i++) {
        printf("%d ", z[i]);
    }

    return 0;
}

Dynamic Arrays

Or dynamically allocated arrays are arrays, that we allocate during the program run and can change their allocated sizes during the process as well. These arrays, unlike statically allocated ones,  allocates only what's needed. If we create an array and we don't know how many elements we should allocate beforehand, we should allocate them dynamically as if we allocate too little, it won't work correctly (overflow) and if we allocate too much, the program will eat too much memory for nothing.

To define a dynamically allocated array, we create an empty pointer first:

int *p_arr;

After that, we can allocate certain block of memory with malloc() or calloc() function from stdlib.h:

#include <stdlib.h> // contains malloc/calloc/realloc functions
...
p_arr = (int *) malloc(10 * sizeof(int));
// or equivalently
p_arr = (int *) calloc(10, sizeof(int));

We have just allocated enough memory for 10 integers (10 times size of integer), in other words, we have just created an integer array p_arr with the size of 10 elements. Accessing these elements can be done the same way as with static arrays:

p_arr[0] = 10;
p_arr[9] = 150;

And after we finish working with our dynamic array or we don't need it anymore, it's good to always free it's allocated memory:

free(p_arr); // basically deletes the array

Let's try it on a simple example - a program, that reads all input as integers and returns their sum. Since we don't know how many numbers will be inputted, we will let the user tell us first:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p_arr;
    int count = 0;
    int sum = 0;

    printf("How many numbers to be sum up: ");
    if (scanf("%d", &count) != 1) {
        printf("Invalid input.\n");
        return 1;
    }

    p_arr = (int*) malloc(count * sizeof(int));

    printf("Enter %d numbers:\n", count);
    for (int i = 0; i < count; i++) {
        if (scanf("%d", &p_arr[i]) != 1) {
            printf("Invalid input.\n");
            return 1;
        }
    }

    for (int i = 0; i < count; i++) {
        sum += p_arr[i];
    }

    printf("Sum of all entered numbers is: %d\n", sum);
    free(p_arr);
    return 0;
}

See the above output:

How many numbers to be sum up: 5
Enter 5 numbers:
1
2
3
4
5
Sum of all entered numbers is: 15

Of course, if we wanted to just sum up all the numbers, we could have came up with a better method and didn't need to use any array at all and it would have been more efficient (no need to allocate any memory). But this example is about arrays so of course I will demonstrate it on them.

Let's take a look on an example where not even the user himself knows how many numbers he will give us to sum up. In this case, we have no other choice but to allocate the memory on the go. Let's allocate 10 numbers first and every time the user input exceeds it, we allocate additional 10 numbers again:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p_arr;             // our dynamic array
    int x;                  // user inputted number
    int maxSize = 0;        // maximum allocated size
    int currentSize = 0;    // current amount of filled elements in our array
    int sum = 0;            // resulted sum

    printf("Enter numbers:\n");
    while (scanf("%d", &x) == 1) { // while we read the input
        if (currentSize >= maxSize) { // if the current amount of elements is already at the maximum
            int *tmp; // we create a temporary array

            maxSize += 10; // extends the maximum allocated size
            tmp = (int *) malloc(maxSize * sizeof(int)); // allocate tmp with this size

            for (int i = 0; i < currentSize; i++) { // fill our tmp with our current p_arr data
                tmp[i] = p_arr[i];
            }

            free(p_arr); // free the memory of p_arr array
            p_arr = tmp; // and assign our tmp to it (p_arr becomes tmp)
        }

        p_arr[currentSize++] = x; // if everything is fine, add an input into our array
    }

    for (int i = 0; i < maxSize; i++) {
        sum += p_arr[i];
    }

    printf("Sum of all entered numbers is: %d\n", sum);
    free(p_arr); // don't forget to free even if it's at the end of the program
    return 0;
}

Looks complicated? Don't worry. This example was just to help you understand how resizing (reallocating) a dynamically allocated array during the program's run works. C's stdlib.h has a function called realloc(), that actually does the exact same thing as we did above.

Let's rewrite the program above to our new version using realloc():

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p_arr;             
    int x;                  
    int maxSize = 0;        
    int currentSize = 0;    
    int sum = 0;            

    printf("Enter numbers:\n");
    while (scanf("%d", &x) == 1) { 
        if (currentSize >= maxSize) { 
            maxSize += 10;
            p_arr = (int*) realloc(p_arr, maxSize * sizeof(int));
        }
        p_arr[currentSize++] = x;
    }

    for (int i = 0; i < maxSize; i++) {
        sum += p_arr[i];
    }

    printf("Sum of all entered numbers is: %d\n", sum);
    free(p_arr);
    return 0;
}

Much better right?

And finally, the same as with the file opening and closing, it's a good habit to check if memory allocation was successful as well. In case the memory is full or for some other reasons the allocation fails and you proceed to work with it as if it didn't, your program might eventually crash.

int *p_arr = (int*) malloc(10 * sizeof(int)); // allocate memory
if (!p_arr) { // if it fails
    free(p_arr); // free it's space
    printf("Allocation failed.\n"); // return error
    return 1;
}

Multidimensional Arrays

An array is basically 1D - one dimensional. It's elements are stored linearly. We can, however, declare and work with 2D (table/grid/matrix) and more dimensional arrays as well.

When declaring a 2D array, we define an array of which each of it's element is actually another array.

#include <stdio.h>
#include <stdlib.h>

int main(void) 
{
    int x[10][10]; // 2D array of 10*10 elements
    int y[10][10][5]; // 3D array of 10*10*5 elements

    int z[3][2] = {
        {1, 2},
        {3, 4},
        {5, 6}
    }; // explicit declaration of 2D array of 3*2 elements

    int **dynamic; // this is gonna be dynamically allocated 2D array using pointer to pointer

    dynamic = (int**) malloc(3 * sizeof(int*)); // allocated 3 pointers
    for (int i = 0; i < 3; i++) {
        dynamic[i] = (int*) malloc(2 * sizeof(int)); // allocate 2 integers to each pointer
    } // finally, we have allocated 3*2 elements

    // print values of z
    printf("Values of z:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            printf("%d ", z[i][j]);
            dynamic[i][j] = z[i][j] * 2; // filling 'dynamic' array
        }
        printf("\n");
    }

    // print values of our dynamic array
    printf("Values of dynamic:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            printf("%d ", dynamic[i][j]);
        }
        printf("\n");
    }

    for (int i = 0; i < 3; i++) { // free memory of each element
        free(dynamic[i]);
    }
    free(dynamic); // finally free the memory of the whole array

    return 0;
}

output:

Values of z:
1 2 
3 4 
5 6 
Values of dynamic:
2 4 
6 8 
10 12 

Strings

If you read the first part of this serial, you already know that C doesn't have explicit data type for strings. Instead, strings are represented as an array of characters in C. Or in other words, strings are represented as:

// string "Hello" is equal to
char str[6] = { 'H', 'e', 'l', 'l', 'o', '\0' }; // '\0' indicates the-end-of-string (similar to EOF in files)

 String declaration can be done in an easier way than the one above:

char str1[6] = "Hello"; // compiler knows it's a string and will add '\0' automatically
char str2[] = "Hello, world!"; // compiler will allocate the size automatically during this initialization
const char *str3 = "Hello, my beautiful world!"; // constant string

C also has some handy functions to work with strings stored in the header file string.h, the functions are:

#include <string.h>
...
strlen(str); // returns the length of given string
strcpy(str, "Hello"); // copies the content of the second argument string to the string from first argument (copies "Hello" to str)
strcat(str, " world"); // appends the content of the second argument string to the string from first argument (if str was "Hello", it will be "Hello world" now)
strchr(str, 'e'); // searches for the character (second argument) in a string (first argument) and returns a pointer to it's location if found or NULL if not
strcmp(str1, str2); // compares 2 strings and returns 0 - identical, >0 - str1 is larger, <0 str2 is larger (lexicographically)
strstr(str1, str2); // searches for a substring (second argument) in a string (first argument) and returns a pointer to it's location if found or NULL if not

What happens when we create a string of 5 characters (char str[] = "Hello";) and fill it with a string of 12 characters? (strcpy(str, "Hello, world");)

Well, the same thing as if we wanted to copy an array of 14 elements into an array of 6 elements. It overflows and the program might eventually crash. The good way to avoid this is to use the secure versions of these functions:

strncpy(str, "Hello, world!", 6); // same as strcpy but copies up to 6 characters of the second argument string
// we can eventually use it as
strncpy(str1, str2, strlen(str1));

Functions

Why should we use functions? Because we are programmers and we write legible, well-structured and non-redundant code. For this purpose, we use functions in C. We have been working with functions all this time (printf(), getc(), fopen(), strcpy(), malloc(), strlen(), ...) but it would be good to be able to write our own functions as well.

Function declaration has this format:

datatype function_name(parameters) {
    body of the function
}

Example of a function that returns the larger of 2 given numbers:

int larger(int x, int y) {
    if (x > y) return x;
    else return y;
}

Calling our function in main() program:

#include <stdio.h>

int larger(int x, int y) {
    if (x > y) return x;
    else return y;
}

int main() {
    int x = 150;
    int y = 85;
    int z = larger(x, y);

    printf("%d\n", x); // prints 150
    printf("%d\n", larger(64, 88)); // prints 88

    return 0;
}

When we pass variables as function arguments this way, the function creates it's own local copy of the variable and works with the copy. So even if we were to change the value of a variable with the same name inside the function, it won't affect the original one:

void playWithNumbers(int x) { // a procedure (function without return value)
    x = 989456; // local variable x doesnt affect the variable x in main() function
}

int main() {
    int x = 5;
    playWithNumbers(x);
    printf("%d\n", x); // prints 5
    return 0;
}

It's safe as it doesn't affect out variables, we don't have to worry about variable names being the same in both functions etc. However, it can also be slow as it has to copy the content of the variable into the local one (which is perfectly fine for small data types like numbers). Imagine copying a large string or large data structure (see Structures bellow) every time we call the function. In some cases, it's better to pass not the value of the variable, but it's address instead and work with pointers to these addresses inside the function instead of their copies. The function then can change the value of our outside variable as well. Check it out on an example of a function, that will swap the values of 2 given variables:

#include <stdio.h>

void swap(int *p_x, int *p_y) { // create local pointers
    int tmp;
    tmp = *p_x;
    *p_x = *p_y;
    *p_y = tmp;
}

int main() {
    int x = 5;
    int y = 40;

    swap(&x, &y); // pass addresses of our variables

    printf("x: %d\n", x); // prints x: 40
    printf("y: %d\n", y); // prints y: 5
    
    return 0;
}

Structures

And finally the last topic of this serial. Structure is a special data type composed of various elements of any datatype (unlike arrays where every element has to be of the same datatype). If you are familiar with OOP (object oriented programming), you can view structures as something similar to objects.

#include <stdio.h>
#include <string.h>

struct person { // first way to define a structure
    char name[10];
    int age;
    double height;
    double weight;
};

typedef struct { // second way
    char name[10];
    char race[20];
    int age;
} dog;

int main() {
    struct person Peter;

    strncpy(Peter.name, "Peter", 10);
    Peter.age = 18;
    Peter.height = 168.5;
    Peter.weight = 64.2;

    dog Rex; // no need to put struct at the start thanks to typedef

    strncpy(Rex.name, "Rex", 10);
    strncpy(Rex.race, "German Shepherd", 20);
    Rex.age = 5;
    
    printf("===== Person 1 =====\n");
    printf("Name: %s\n", Peter.name);
    printf("Age: %d\n", Peter.age);

    printf("===== Dog 1 =====\n");
    printf("Name: %s\n", Rex.name);
    printf("Race: %s\n", Rex.race);

    return 0;
}

Output:

===== Person 1 =====
Name: Peter
Age: 18
===== Dog 1 =====
Name: Rex
Race: German Shepherd

Because structures can be really large, we usually work with pointers to them (to their addresses) than with their actual contents (for example when passing structures to functions - see Functions above).

struct Person *p_peter = &peter;
(*p_peter).age = 18; // accessing the attribute of the structure through pointer p_peter
p_peter->age = 18; // can be actually written this way as well

 

Thank you for reading up to this point. Again, if you have any questions, comments or found any errors regarding this article, contact us directly via our Facebook.