Intermediate Concepts in C

Data Types, Memory Management, and Error Handling

Introduction

C is a low level programming language which allows the programmer to code more closely to computer language. This is why C is a little more hands on when it comes to its data types, memory management, and error handling. Unlike JAVA, C does not have functions that automatically catch and throw errors, and C does not have a Garbage Collector tool to clean up the variables that exist in the memory but are no longer being used. This is why it is important to understand how to perform these actions in C by yourself, to make sure you have the most efficient program as you can possibly have.

Data Types

Just like the majority of other programming languages (such as JAVA and C#), C utilizes different data types to hold values assigned to memory locations (or variables). These data types are responsible for specifying the amount of space that a variable will occupy in the memory. In C programming, these data types can be grouped into 4 main categories:

Basic Types

Basic types variables, for lack of a better word, hold the most basic types of data that can be stored in memory. Basic type variables hold arithmetic types, and can be broken down even further into two different categories:

  • Integer Types
All the Integer Basic Types in C
  • Floating-Point Types
All the Floating-Point Types in C

Floating-Point data types can only be used if you include the float.h header file in your program.

A simple program using the float data type.

The result from this printf statement will be:

Look at me using Floating-Points! 4.560000

Enumerated Types

Enumerated types are a unique way to create your own data types. They are generally used to make the program easier to read and maintain.

For example:

To define an enumerated type, the programmer must type the following: typedef enum{}*name* where *name* is equal to the name of the enumerated type being created. Inside the curly brackets, different sets of data can be defined that the created enumerated type can be equal to, with each value being comma separated.

Declaring an Enumerated Type called FarmAnimal that can hold five different animals.

By creating the FarmAnimal enumerated type, FarmAnimal variables can now be created. An animal can be specified by specifically saying FarmAnimal animal = chicken;, or the value can be specified with an int value ranging from zero — size of the enumerated type (FarmAnimal otherAnimal = 2;).

The FarmAnimal types can be declared in different ways.

Derived Types

Derived Types are formed by combining one or more basic data types. There are five different variations of derived types:

Function Types — A function type describes a function that returns a value of a specified type. For example, if a function returns an int, the function type would be of type int.

A function type of int from returning the sum of the expression.

Pointer Types — A pointer type describes the value that is represented in a memory location. To pass a reference to a variables location, the syntax is &x (where x is the variable you wish to take the address of). To grab the value from a pointer location, the syntax is *x (where x is the pointer for a memory location). For example, passing an address and retrieving its value:

Passing the address of int x and y, then setting their value.

Array Types — An array type can be formed from any data type. The size of the array is defined by its data type. The size of the array can be automatically created by leaving the square brackets of a declared array empty (char x [] = “Hello”; — which will create an array of size 6 (5 bytes for the letters, 1 byte for the a trailing \0)), or it can be specified by placing an int in the square brackets (char y[5] = {‘H’,’e’,’y’,’\0'}; — which creates an array of size 5 with the first 3 memory locations holding the chars that spell “Hey”). When creating a character array (like a String in JAVA), the last value in the array will normally be \0 to specify the end of the string.

Structure Type — Structure types are made up of sequentially allocated object sets called members (which is like an object with attributes in JAVA). To declare a structure type, the syntax is struct x{…}; (where x is the name of the structure and  would be different data types that make up the structure). The structure can then be instantiated with variables by calling struct x varID; (where x is the structure name and varID is the name of the variable).

To change the value of a member in a structure, the syntax is structureVar.member = x (where structureVar is a struct variable, member is a member in the structure, and x is the new value of the specified member).

A structure type declared as teacher with two attributes within the structure called id and name.

Union Types — Union types can store objects of different types in the same memory location. Union types can be useful for interpreting the value of a data type as another data type, which can be useful for signal handling. Using union types can be an efficient way to use the same memory location for multiple purposes.

A stats union.

Void Types

The void data type quite literally means “nothing”, or no type. Void data types are typically used in three ways:

Function Argument — Void values can be used to indicate that a function takes nothing as a parameter.

A void function argument.

Function Return Value —Void can be used to indicate that a function returns nothing.

A function with a return type of void.

Generic Data Pointer — Void can be used as a pointer to data of an unknown type, and cannot be dereferenced.

ex. void*data;

Memory Management

In JAVA, the Garbage Collector is used to dynamically allocate and release memory inside a program. C on the the other hand uses a variety of memory management functions that are imported with a preprocessor command (#include <stdlib.h>). There are four main types of memory management functions:

void *calloc(int x, int size);

This function will allocate an array of x elements, with each element having a size (in bytes) of size.

ex. calloc(int x, int 8);

void *malloc(int x);

This function will allocate an array of x elements, but leaves the size uninitialized.

ex. comments = malloc( 40 * sizeof(char));

void *realloc(void *address, int newsize);

This function will reallocate a memory location (address)to its specified newSize.

ex. comments = realloc(comments, 200* sizeof(char));

void free(void *address);

This function is used to free up the memory in the location specified.

ex. free(comments);

Memory Management in Action

This simple program allocates, reallocates, and then frees up the memory required for the comments variable.

An example of memory management.

To manipulate the string, the functions strcpy (changes the makeup of the string) and strcat (appends to the string) are used.

Error Handling

C does not support direct error handling (unlike JAVA which contains exceptions), but you are able to access the return values of functions directly that indicate an error has occurred.

The <errno.h> header provides functions that can be used to show any text associated with an error by using:

perror() — This function will display the string that you pass to it, followed by the textual description of the error that occurred.

Basic use of perror.

strerror() — This function will return a pointer to the textual representation of the current error. The stderr file stream is used to output the errors.

Basic use of strerror.

Program Exit Status

To indicate that a program has run successfully (or unsuccessfully), it is common to place an exit status at the end the programs main function. The int value that is returned from an exit status is either 0 (no errors) or 1 (errors have occurred).

This allows the computer to recognize whether or not the the program ran as it should, or if there were problems with the application that prevented the program from performing the task properly.

This simple calculator indicates if the program ran successfully when dividing two numbers.