Top 5 advanced C programming concepts for developers
Tags: c, tutorial, cprogramming, codequality
Published: 2020-11-02 21:25:39

Due to the adoption of C as the go-to language for self-driving car development, C is making a resurgence in popularity and demand. Many industry veterans learned programming through C but haven't used it in years. 

Today, we'll help you refresh your C knowledge or take your  learning to the next level with the 5 most important advanced C concepts for developers.

**Here’s what we’ll cover today:**
 
* [1. Dynamic memory allocation](#dynamic)
* [2. Debugging with gdb](#debugging)
* [3. Function pointers](#pointers)
* [4. Recursion in C](#recursion)
* [5. Typecasting and typedef in C](#typecasting)
* [What to learn next](#next)

<br>

 
<h3> <b> Become a certified C programming master</b>
 
Refresh your C knowledge fast and receive an Advanced C Certificate to amaze your next interviewer.
 
<b>[Advanced Programming Techniques in C](https://www.educative.io/courses/advanced-programming-techniques-in-c)


<br>
 
<a name="dynamic"></a>
## 1. Dynamic memory allocation
### Definition
In C, there are two types of memory allocation: static and dynamic. Static allocation is the more basic of the two and is allocated to the Stack at execution. 

Once allocated, static allocation has a fixed size. Static allocation is used for any global variables, file scope variables, and variables with the `static` keyword.

**Dynamic memory allocation** is the more advanced of the two that can shift in size after allocation. This memory is stored in the Heap. Unlike the Stack, Heap memory has no variable size limitation. 

Dynamic allocation allocates more memory as it's needed, meaning that you never have more memory allocated than you need.

Dynamic memory allocation also has performance downsides. Cached heap data is not adjacent like cached Stack data. As a result, retrieving heap data is often less efficient than retrieving Stack data. 

Dynamic memory allocation also costs a large **overhead**. Variables cached to the Heap must have an associated pointer of equal size to locate the variable later. 

For example, a 4-byte variable would also have an associated 4-byte pointer. This means that Heap data has roughly double the resource cost of Stack data.

While dynamic memory can scale up to allow for more data, it does not automatically deallocate memory when the amount of data is reduced. You must instead manually deallocate memory using the `free()` function. 

You'll quickly find yourself with a memory leak error if you forget to deallocate your memory that can slow or even crash your program!

<br>

### Uses
Dynamic memory's overhead cost is best used for select variables like scaling data structures. It's best to still use static allocation for the majority of variables to avoid slowing your program.

You should use dynamic memory when:
* You don't know how much memory a structure will need beforehand
* You want a data structure that can infinitely scale to match the input
* You want to ensure that you never over-allocate memory in advance (automatic upward scaling)
* You're using a linked list or structure object



<br>

### Implementation

```c
#include <stdio.h>
#include <stdlib.h> 
int main( )
{
    // Declare pointer variables
    int  *i ;
    float  *a ; 
    /* Allocate memory using malloc and store the starting 
    address of allocated memory in pointer variable*/
    i = (int*) malloc (sizeof(int));
    a = (float*) malloc (sizeof(float));
    // Declare employee structure
    struct emp
    {
        char name [20];
        int age;
        float sal;
    };
    // Declare structure pointer
    struct emp *e;
    e = (struct emp *) malloc (sizeof (struct emp));
}
```

In **lines 6,7, and 20**,  `i`, `a`, and `e` are created on the stack. All three are pointers. The memory chunks they are pointing to are created on the heap.

In **lines 10-11**, we use the `malloc( )` library function to implement dynamic memory allocation. In dynamic memory allocation, both decisions—how much memory to allocate and where to allocate—are made during execution. 

The number of bytes that should be allocated is passed to `malloc( )` as an argument. `malloc( )` allocates that much memory and returns the base address of the memory chunk allocated as a void pointer.

The void pointer returned by `malloc( )` is appropriately typecasted (converted) into an integer pointer, float pointer, or struct emp pointer using the (target type) syntax. 

<br>
 
<a name="debugging"></a>
## 2. Debugging with gdb
### Definition
Linux is the most commonly used OS for C programming. Linux has a debugging command-line tool called **gdb** that will help you debug your program. Once installed, you can run your entire program using gdb and it will point out both logical and syntactical errors. 

This  allows you to:
* Execute a program, one statement at a time
* Stop an execution at any given line/function of the program
* Print the intermediate values
* Understand the detailed flow of execution



<br>

### Uses
The most important commands in gdb are breakpoints, `step`, `next`, and variable value printing.

**Breakpoints** pause a program's execution at a designated time to allow you to evaluate the program status and variable values. This tool is used to break up larger sections of code into smaller chunks. You can then run these chunks to determine the general location of the bug.

**Step** lets you run code one line at a time. This command executes the next line of code then pauses for further instruction. You'll use this once you've narrowed the bug down to a smaller code chunk but not a specific line. As you step through the chunk, you'll eventually find which specific line causes the bug.

**Next** is similar to `step` but instead executes the next function rather than the next line. This is best used in tandem with breakpoints to debug function-heavy or branching programs. You can run each modular function and make sure each performs as expected.

**Variable value printing** used after a breakpoint, `step`, or `next` command to print the current value of the variable. Without gdb, you'd have to manually write print commands into your program after each time it interacts with the variable. Instead, gdb allows you to keep your program clear of cluttering print commands while still allowing you to track a variable's value through a program segment. 

<br>

### Implementation
**Install:**
```
apt-get -y install gdb
```

<br>

**Compile the program using gdb compiler:**
```
gcc main.c -g
```

<br>

**Enter gdb mode:**
```
gdb a.out
```
<br>

**See source code:**
```c
list
```

<br>

**Create a breakpoint:**
```
gdb) break 6
gdb) run
```

<br>

**Step one line:**
```
gdb) step
```

<br>

**Step one function:**
```
gdb) next
```

<br>

**Check variable's current value:**
```
print {variable name}
```

> Pressing `enter` while in gdb renters the previous command. Use this to quickly step through code blocks without retyping the `step` or `next` commands each time.

<br>
 
<a name="pointers"></a>
## 3. Function pointers
### Definition
Function pointers are another way to call a created function. The standard function call is with the functions name and parentheses, `function1()`. Function pointers allow you to call a function with the function's memory location, `(*f)()`.

To do this, you must first store the location of the desired function in a pointer variable. Once you do this, you can use the function pointer in place anywhere you'd use the standard function call.


<br>

### Uses
Function pointers allow you to **pass functions as arguments** to other functions or data structures. You can therefore avoid the use of temporary variables and instead simply pass the function's result directly to the next function. 

Function pointers also prevent repeated code for different data types. For example, normally to create a quicksort program for different data types you'd need to write type-specific sort functions like `quicksort_integer` or `quicksort_char`. 

With function pointers, you could instead create a function that takes pointers as arguments and sort by any field that you have a comparator for. 

Finally, function pointers allow you to create structs populated with functions rather than variables. For example, you could create an array of functions by storing each function pointer as an element.

Use function pointers when you want to plug one function into another or when you want to ease type restrictions when using data structures.


<br>

### Implementation

```c
# include <stdio.h>
void dbl ( int * ) ;
void tple ( int * ) ;
void qdpl ( int * ) ;
int main( )
{
    int  num = 2, i ;
    void ( *p[ ] )( int * ) = { dbl, tple, qdpl } ;
    for ( i = 0 ; i < 3 ; i++ )
    {
        p[ i ]( &num ) ;
        printf ( "%d\n", num ) ;
    }
    return 0 ;
}
 
void dbl ( int *n )  
{
    *n = *n * *n ;
}
 
void tple ( int *n )  
{
    *n = *n * *n * *n ;
}
 
void qdpl ( int *n )  
{
    *n = *n * *n * *n * *n ;
}
```

In this program, we have defined three functions. The `dble`, `tple`, and `qdpl` functions receive the address of an int, doubles/triples/quadruples the int value, respectively, and returns nothing.

In the main function, we store the addresses of these functions in an array and, then, call them one by one by iterating through an array.

In **line 8**, we create an array of a function pointer and store the addresses of `dble`, `tple`, and `qdpl` in an array.

In **line 11**, we call the `dbl`, `tple`, and `qdpl` functions by their addresses by iterating through an array.

<br>
 
<div style="background-color:#F1F1FF;padding-left: 10px;padding-top:1px;padding-bottom:1px;">
 
## Keep learning about Advanced C
Refresh your C skills quickly, without scrubbing through tutorial videos. Educative's courses are skimmable and feature interactive coding exercises designed specifically for developers like you.
 
<b>[Advanced Programming Techniques in C](https://www.educative.io/courses/advanced-programming-techniques-in-c)  </b>
</div>
<br>

<br>
 
<a name="recursion"></a>
## 4. Recursion in C
### Definition
[Recursion](https://www.educative.io/blog/recursion) is when a function contains a call to itself. Recursive programs will often contain commands and operations above the recursive call that are repeated in each recursive iteration. 

In C, you can recursively call both user functions and `main()`. Recursion can **replace traditional loops** in many circumstances. Like loops, recursive programs can loop infinitely if programmed without an exit condition.

Typical recursive calls either come within an `if` segment or an `else` segment.

For `if` type recursive programs, the recursive call is held in the `if` segment. The program will continue to iterate until the `if` becomes `false`. Then, the program will progress to the `else` segment that contains the return statement. 

For example:
```c
int example()
{
  if (n != 0){ 
    // do something
    // something more
    example(..); //recursive call
  }
  else 
  {
    return 1;
  }
}
```

In the `else` type programs, the recursive call is held in the `else` segment along with operations that affect the program conditions. Any `else` type program iterates until the `if` statement becomes `true`.

 The difference between these program types is if we're progressing *away* from a starting state or *toward* a target state.

For example:
```c
int example ()
{
  if (n == 0){
      return 1; 
  }
  else 
  {
    // do something
    // something more
    example(..); //recursive call
  }
}
```


<br>

### Uses

Recursion is used to reorient problems to focus on the desired condition rather than the number of iterations to achieve it. 

With loops, you must program the number of iterations the program will take. This means you have to know **how many iterations** are needed for a given input or make a subcomponent that can determine the number of iterations.

However, you're ultimately concerned with achieving a certain goal and the iterations are your tool to do so, not visa versa.

Recursion allows you to design programs that foreground that goal-based thinking. Recursive programs repeat however many times it takes to achieve their goal, whether that be to deviate from a starting state or to reach a target state. 


You shouldn't use recursion for everything because it uses more resources than iterative loop solutions. In general, recursion is best used for solutions that:
* Can be solved using answers from many smaller sub-problems
* Require repeating of the same steps multiple times
* Pursue certain program states.


<br>

### Implementation
This C program finds the factorial of a passed integer argument. This is an `else` type recursive program.

```c
#include <stdio.h>
int refact ( int ) ;
 
int main( )
{
    int  num, fact ;
    num = 4;
 
    fact = refact ( num ) ;
    printf ( "Factorial value = %d\n", fact ) ;
 
    return 0 ;
}
 
int  refact ( int  n )
{
    int  p ;
 
    if ( n == 0 )   
        return ( 1 ) ; 
    else
        p = n * refact ( n - 1 ) ; //recursive call
 
    return ( p ) ;
}
```

<br>
 
<a name="typecasting"></a>
## 5. Typecasting and typedef in C
### Definition
Typecasting is a special type of operation in C that one data type to another. Typecasting can be done either implicitly or explicitly.

**Implicit** typecasting is where the compiler automatically converts data. Implicit typecasting is easier to implement but often loses information during the conversion process.

```c
// implicit typecasting
# include <stdio.h>
int main( )
{
    // Initialize variables of type int
    int  a = 5, b = 2, c ;
    // Declare variable of type int
    float  d ;
    // Assign value to c
    c = a / b ;
    printf ( "%d\n", c ) ;
    // Store result of integer division in variable of float type
    d = a / b ;
    printf ( "%f\n", d ) ;
}

```

Here, `d` should have the value of `2.500000` but it's actually `2.000000`. The decimal value was truncated during automatic conversion as both `a` and `b` are integers and therefore return an integer value from their division.



**Explicit typecasting** solves this problem. With this form of typecasting, you instead manually convert the data and therefore can select an earlier conversion point. 

```c
// explicit typecasting
# include <stdio.h>
int main( )
{
	// Initialize variables of type int
	int  a = 5, b = 2, c ;
	// Declare variable of type int
	float  d ;
	// Assign value to c
	c = a / b ;
	printf ( "%d\n", c ) ;
	// Store result of float division in variable of float type
	d = ( float ) a / b ;
	printf ( "%f\n", d ) ;
}
```

On **line 12**, `d = ( float ) a / b`, a is typecast forcibly into a float *before* performing the division operation. The operation, therefore, maintains all decimal value from the operation. As you can see, the value of `d` is the `2.500000` when explicitly cast.

> Only convert from less specific data types to more specific data types to avoid data loss. For example, you'll keep all information if you convert from`int` → `double` but you'll lose information if you convert from `double` → `int`.

<br>

### Uses
You can use typecasting to create **reusable code**. If you convert from lesser to greater data, you can use the same values as arguments for multiple functions even if they call for different data types. 

You can also take the output from the first of a function sequence and cast it to a new type to fit the needs of whatever next function it hits. Typecasting allows you a greater degree of freedom when using the strict data types in C.

You can also typecasting to assign new names to existing data types or user-defined structs with the `typedef` keyword. This does not un-assign the original name but simply adds another way to refer to that same type.

For example:

```c
#include<stdio.h>

int main() {
    // Declare variables
    unsigned long int i, j;
    // Give new name to unsigned long int
    typedef unsigned long int ULI;
    // Declare variables of type ULI
    ULI k, l;
}

```

Here we assign the name `ULI` to the `unsigned long int` type. We can then declare variables of type `ULI` or `unsigned long int` and achieve the same effect. 

This can help readability as you could assign names to data types to shorten declarations or more accurately represent what values they'll hold (`string` → `color`).

<br>

### Implementation
**Implicit typecasting:**
```c
// implicit typecasting
# include <stdio.h>
int main( )
{
    // Initialize variables of type int
    int  a = 5, b = 2, c ;
    // Declare variable of type int
    float  d ;
    // Assign value to c
    c = a / b ;
    printf ( "%d\n", c ) ;
    // Store result of integer division in variable of float type
    d = a / b ;
    printf ( "%f\n", d ) ;
}
```

<br>

**Explicit typecasting:**

```c
# include <stdio.h>
int main( )
{
	// Initialize variables of type int
	int  a = 5, b = 2, c ;
	// Declare variable of type int
	float  d ;
	// Assign value to c
	c = a / b ;
	printf ( "%d\n", c ) ;
	// Store result of float division in variable of float type
	d = ( float ) a / b ;
	printf ( "%f\n", d ) ;
}
```

<br>

**Typedef for user struct:**


```c
#include<stdio.h>

int main() {
    // Declare structure
    struct a
    {
        char *p;
    };
    // Give new name to struct a
    typedef struct a FILE;
    FILE *fs , *ft;
}
```

<br>
 
<a name="next"></a>
## What to learn next
Congratulations on completing your first steps into advanced C programming! We covered the 5 most important concepts to improve your C programming skills.

However, these are just the surface of what this long developed language has to offer. Some good next topics to study are:
* Creating libraries
* C with Linux/Unix
* Bits and bitwise operators
* Variable argument lists
* Structures

Educative's new course **[Advanced Programming Techniques in C](https://www.educative.io/courses/advanced-programming-techniques-in-c)** will teach you all these topics and more with interactive exercises and expert tips. By the end of the course, you'll have mastered this classic programming language, completed dozens of practice projects, and even have a **[certificate](https://www.educative.io/blog/educative-new-feature-rundown)** to use for a resume or portfolio.

*Happy learning!*

<br>

### Continue reading
* [Now is the perfect time to learn C](https://www.educative.io/blog/now-is-the-perfect-time-to-learn-c)
* [Applied Data Science: serverless functions, pipelines, and PySpark](https://www.educative.io/blog/applied-data-science-pyspark-serverless-functions)
* [Computer Number Systems 101: Binary & Hexadecimal Conversions](https://www.educative.io/blog/computer-number-systems-binary-hexadecimal-conversions)