Keywords in C Programming

Keywords in C Programming

·

21 min read

Keywords are the reserved words in every programming language, which are basic building blocks of program statements. Keywords are as essential as understanding the structure or syntax of the programming language. One cannot master the programming language without learning the proper use of keywords.

In C programming, there are 32 keywords, which must be written in lowercase only. There are multiple resources on keywords, they are also mentioned in all books written on C Programming. But yet hard to remember all of them, thus, here I have written an easier way to explain each one of them and also organized them to help you to remember them.

These 32 keywords are as follows alphabetically

autodoubleintstruct
breakenumlongswitch
caseelseregistertypedef
charexternreturnunion
constfloatshortunsigned
continueforsignedvoid
defaultgotosizeofvolatile
doifstaticwhile

YES, absolutely they are difficult to remember in this order, here is how I organized these keywords to remember them

Fundamental Datatypes

Starting with five fundamental datatypes, the first program we write is Hello World!, in which we have seen void main() (or int main()) as our first function, here, void (or int) is called a function return type, which can be also declared along with the variables as their datatype.

All these keywords follow the same format

    datatype variable;

Type Modifiers, Qualifiers, and Size

allows modifying the range and size of the datatypes

Basic Collections

the primitive data structures, yet the collection of the variables

User-defined declarations

allows user to define their own declarations

Fucntion Return

all these data types, collections, and type defines are returned by

Storage class specifiers

allows declaring explicitly the scope and lifetime of variables

Conditional blocks

allows creating branches in code flow that are directed by conditions

Jumps

allows to jump out of the loop or jump back to the loop by skipping further statements inside the loop

Loops

allows execution of the block of code several times according to the condition given in the loop. In simple words, executing the same code multiple times is called a loop


Now, let's go through them one by one

void

  • it means nothing or no value
  • often used to specify the type of a function that doesn't return any value to the calling function
void fun(int a) {
    // returns no value
    // or returns nothing
}
  • we cannot declare a variable as void, as it will throw an error
  • can also be used as a function parameter at the time of declaration, when there is no argument passed to a function
int fun(void) {
    int a = 5;
    return (a + a);
}

int main(void) {
    int x = fun();
}

char

  • single character datatype, which consumes 8-bits (1-byte) only
  • ranging from -128 to 127 when signed and from 0 to 255 when unsigned
  • all characters in an ASCII table can be displayed with this datatype
char alpha = `A`
  • can be assigned integer values ranging from -128 to 127 (signed) or from 0 to 255 (unsigned), stores respective ASCII character
char alpha = 23;
unsigned char beta = 200;

printf("%c, %c", alpha, beta);

int

  • used to declare the whole number
  • consumes 16-bits (2 bytes) on 16-bit processors/machines or 32-bits (4 bytes) on 32-bit processors/machines
  • ranging from -32768 to 32767 when signed and from 0 to 65535 when unsigned
  • range can be extended, which is explained later
int i = 56;
unsigned int j = 51983;
  • can be assigned character values as well, stores respective ASCII number
int ch = 'E';

printf("%d", ch);

float

  • used to declare floating-point (or real) number
  • consumes 32-bits (4 bytes) with 6 digits of precision
  • ranging from 3.4E-38 to 3.4E+38
  • signed by default, unsigned is not permitted
float num = 34.67153982;

printf("%f", num);

output:

34.671539

double

  • the extended version of float, also denoted as double precision numbers
  • consumes 64-bits (8 bytes) with 14 digits of precision
  • ranging from 1.7E-308 to 1.7E+308
  • range can be extended further, which is explained later
double extNum = 1467.7987543022;

printf("%lf\n", extNum);    // print value normally
printf("%.12lf\n", extNum); // force to print value for 12 decimal places

output:

1467.798754
1476.798754302200

short, long, signed, and unsigned

  • called as a qualifier, which can be assigned to int, char, or double datatypes as a type modifiers
  • signed uses one bit (MSB) for the sign (+/-) and other remaining bits for the magnitude of the number, whereas unsigned uses all bits for the magnitude of the number
  • signed is the default to all data types, there is no need to specify it, whereas unsigned can be used for int and char only
  • short and long are used to specify the storage of a variable datatype, where short can be used for int to specify 16-bit (2 bytes) integers
  • long extends the range and storage capacity further for int and double datatypes, where long int consumes 64-bit (8 bytes) on all processors/machines, ranging from -2147483648 to 2147483647
  • long double consumes 80-bits (10 bytes) (some machines consume 128-bits (16 bytes)) along with extending the precision further, ranging from 3.4E-4932 to 3.4E+4932
char                // signed char
unsigned char

int                 // signed int
unsigned int
short int           // signed short int
unsigned short int
long int            // signed long int
unsigned long int

float
double
long double

struct

  • As we know Array cannot be defined with different datatypes inside it, so structures are created to resolve the limitation of grouping variables with different data types together
  • Structures are the constructed datatypes in C, their general syntax is as follows
struct tag_name {
    datatype member1;
    datatype member2;
    ...
    datatype membern;
};
  • the keyword struct defines the structure format
  • the tag_name can be used to declare structure variables of its type, later in the program
  • variables defined inside the structure are structure elements or members
  • the structure format is called a template, note that it has not declared any variables
  • even if the entire definition is considered as a statement, each member is declared independently for its name and type in a separate statement inside the template
  • the following example will help us to understand this better
book: author(char), title(char), price(float), year(int)

struct book {
    char author[20];
    char title[15];
    float price;
    int year;
};
  • The detailed guide on Structures shall be uploaded individually, I shall update the link here

union

  • Union is the same as Structures, hence, follows the same syntax. However, there is a major distinction between them in terms of storage.
union tag_name {
    datatype member1;
    datatype member2;
    ...
    datatype membern;
};
  • Each member in the structure has its own storage location, where all the members of a union use the same memory location
  • It can handle only one member at a time, as only one memory location is allocated to the union
struct book_struct {
    char author[20];
    char title[15];
    float price;
    int year;
} var_book_struct;

union book_union {
    char author[20];
    char title[15];
    float price;
    int year;
} var_book_union;

int main() {
    printf("Size of Structure: %ld\nSize of Union: %ld\n", 
            sizeof(var_book_struct), sizeof(var_book_union));

    return 0;
}

output:

Size of Structure: 44
Size of Union: 20
  • The detailed guide on Unions shall be uploaded along with structures, I shall update the link here

enum

  • User-defined data type called enumerated datatype
enum identifier {value1, vlaue2, ..., valuen};
  • can be used to declare variables that can have values enclosed within the braces known as enumeration constants
  • enumeration constants has a default data type of int
  • identifier is a user-defined enumerated datatype and value1, value2, ..., and valuen are enumeration constants
  • the default value assigned to the enumeration constants starts from 0 (zero) if not specified and follows the order
enum days {
    sunday,     // value = 0
    monday,     // value = 1
    tuesday,    // value = 2
    wednesday,  // value = 3
    thursday,   // value = 4
    friday,     // value = 5
    saturday,   // value = 6
};

// enumerated constants can be assigned 
// directly to the integer variables
int day = tuesday;
printf("day: %d\n", day);

output:

day: 2
  • After defining enum we can declare the variables of this new datatype
enum days {
    sunday,     // value = 0
    monday,     // value = 1
    tuesday,    // value = 2
    wednesday,  // value = 3
    thursday,   // value = 4
    friday,     // value = 5
    saturday,   // value = 6
};

// declare the variable with the enumerated datatype
enum days weekend = 6;

if (weekend == saturday)
    printf("it's the weekend!\n");

output:

it's the weekend!
  • however, values can be assigned manually to the enumeration constants
enum days {
    monday = 1,     // value = 1
    tuesday,        // value = 2
    wednesday,      // value = 3
    thursday,       // value = 4
    friday,         // value = 5
    saturday,       // value = 6
    sunday = 0,     // value = 0
};

printf("Day: %d\n", sunday);

output:

Day: 0
  • the definition and declaration of variables can be combined into one statement
enum days {
    sunday,     // value = 0
    monday,     // value = 1
    tuesday,    // value = 2
    wednesday,  // value = 3
    thursday,   // value = 4
    friday,     // value = 5
    saturday,   // value = 6
} weekend = 6, weekbeg; // enumerated variables

weekbeg = monday;    // value can be assigned individually
  • enums are better than #define macros, as they are type-safe and define a syntactical element
// don't prefer this
#define success 1
#define failure 0

// prefer this
enum bools {
    failure,    // value 0
    success,    // value 1
};

typedef

  • allows users to define their own datatype which can be declared with the existing datatype and later can be used to declare the variables with the defined type
typedef type identifier;
  • type refers to an existing datatype and identifier refers to the new name given to the datatype.
  • creates the reference to an existing datatype, doesn't create a new type
// declare color as a new datatype to easily understand
typedef char color;

// declare variables of datatype color
color red = 'r', green;
green = 'g';
  • advantageous in creating some new datatype names for easiness in readability
  • can also be used for struct and union, which helps to write more organized code

return

  • specifies the return value of the function with the return type
  • can also be used to terminate the function in conditional statements
  • optional for void functions
void fun(int x) {
    // statements

    return; // optional
}

or

void fun(int x) {
    // statements
}

sizeof

  • evaluates the size of a variable or a constant
sizeof(variable/const/type);
  • it is also applicable to the existing data types as well as user-defined
  • returns the value which indicates the total bytes consumed by a variable or a constant
  • the return type is generally long unsigned int, which can be different in different processors/machines
#include <stdio.h>

enum month {
    jan,
    feb,
};

int main() {
    float fl = 3.45;

    printf("%u bytes\n", sizeof(char));   // read size of a datatype
    printf("%ld bytes\n", sizeof(jan));   // read size of a constant
    printf("%ld bytes\n", sizeof(fl));    // read size of a variable

    return 0;
}

output:

1 bytes
4 bytes
4 bytes

auto

  • used to declare a local variable within a function, which has its scope within a function
auto type variable;
  • auto is assigned to all local variables by default, without specifying it
int fun(char ch) {
    int x;  // storage class auto assigned automatically or by default
    // memory assigned to x automatically
    x = (int )ch;

    return x;
    // memory destroyed assigned to x as its scope has ended
}
  • a variable with auto keyword initialized to undefined or garbage value, until and unless assigned manually
void fun() {
    auto int x;

    printf("x: %d\n", x);
}

int main()
{
    fun();

    return 0;
}

output:

x: 22047 (garbage value)

static

  • declares a variable, which remains in the memory until the end of the program
static type variable = value;
  • static variables are automatically initialized to zero
void fun() {
    static int x;   // automatically assigned value 0

    printf("x: %d\n", x);
}

int main()
{
    fun();

    return 0;
}

output:

x: 0
  • a local variable with static keyword exists and retains its value even after the control is transferred to the calling function
int fun() {
    static int count = 0;
    count++;    // increment the variable

    return count;
    // memory assigned to static variable not destroyed
}

int main() {
    printf("%d ", fun());
    printf("%d ", fun());

    return 0;
}

output:

1 2
  • static keyword can also be assigned to the user-defined functions, to limit their access within the file, as functions are by default global
  • this can be also useful to reuse the same function name in other files
// file_1.c
static void display(void) {
    printf("displaying display function\n");
}

// file_2.c
int main() {
    display();

    return 0;
}
  • the above code will throw an error of undefined reference to display
  • assigning static keyword to structure elements is not permitted

extern

  • allows extending the visibility of a variable or a function, this means a variable must be defined somewhere in the program, and declaring it as extern will make it global
  • when extern is used with a variable, it's only declared, not defined
int x;          // declaration and definition, memory allocated
extern int x;   // declaration only, memory not allocated
  • generally used when a particular file needs to access a variable from another file
  • to declare a variable as an extern it must be defined somewhere in the program
#include <stdio.h>

extern int x;   // declared x as extern but no memory allocated

int main() {
    // display x
    printf("x: %d\n", x);

    return 0;
}

output:

undefined reference to `x'
#include <stdio.h>

extern int x;   // declared x as extern but no memory allocated

int main() {
    int x = 45; // declared and defined, memory allocated
    // display x
    printf("x: %d\n", x);

    return 0;
}

output:

x: 45
  • As an exception, declaring an extern variable with initialization is permitted, yet it throws a warning and is not recommended
extern int x = 45;

int main()
{
    printf("x: %d\n", x);

    return 0;
}

output:

warning: ‘x’ initialized and declared ‘extern’
 | extern int x = 45;
 |            ^
x: 45
  • declaring extern keyword without defining, accessing, or initializing it also works fine
extern int x;   // only declaration, no memory allocated

int main()
{
    printf("Hello World!");

    return 0;
}

output:

Hello World!
  • since the functions are global by default, the use of extern keyword for functions doesn't needed

register

  • allows storing variables inside CPU register instead of memory
  • Variables that are used frequently can be stored inside the register so that they can be accessed faster
  • register variables have scope until execution of block they are declared inside
int main() {
    register int x = 10;    // declared and defined inside register with value 10

    printf("x: %d\n", x);

    return 0;
}

output:

x: 10
  • using time command in Linux for the programs without and with register variable shows the difference between execution time
without register keyword
$ time ./test_register_variable
x: 10

real    0m0.001s
user    0m0.002s
sys     0m0.000s


with register keyword
$ time ./test_register_variable
x: 10

real    0m0.001s
user    0m0.001s
sys     0m0.000s
  • register variables cannot be declared globally
register int x = 10;

int main() {
    printf("x: %d\n", x);

    return 0;
}

output:

error: register name not specified for ‘x’
  • register variables are initialized to a garbage value by default
  • most modern compilers store frequently used variables as register automatically, so even if you don't create any register variable, compilers will do it by themselves

const

  • allows declaring a variable as a constant, this means the value of the variable will not be changed throughout the program execution
  • can be written even after the datatype
const int a = 5;    // allowed
int const a = 5;    // allowed
  • if we try to change the value manually it will throw compile error, this is also applicable to value modification by any other function or operations like increment/decrement
const int a = 5;

int main()
{
    a = 10; // or a++ or --a

    return 0;
}

output:

error: assignment of read-only variable ‘a’
  • assigned value 0 (zero) as default if not initialized manually at the time of declaration
const int a;

int main() {
    printf("a: %d\n", a);

    return 0;
}

output:

a: 0
  • however, the pointing value can be modified and assigned to a constant pointer
int a = 10; // non-constant integer variable
const int * ptr = &a;   // pointer is assigned address of a

int main()
{
    printf("*ptr: %d\n", *ptr);
    ++a;
    printf("*ptr: %d\n", *ptr);

    return 0;
}

output:

*ptr: 10
*ptr: 11
  • in the case of pointer to constant works little different
int a = 10;
int b = 20;
int * const ptr = &a;

int main()
{
    *ptr = b;
    printf("*ptr: %d\n", *ptr);
    ++a;
    printf("*ptr: %d\n", *ptr);

    return 0;
}

output:

*ptr: 20
*ptr: 21

volatile

The volatile keyword is used for creating volatile objects. A volatile object can be modified in an unspecified way by the hardware.

  • used to tell explicitly the compiler that a variable's value may be changed at any time by some external sources (from outside the program)
  • as the value can be modified of a volatile variable even by its own program, we can add const keyword to keep the value unchanged by its own program but can be altered by some other process/external program
const volatile number;

here, a number is a volatile object.

Since a number is a constant, the program cannot change it. However, hardware can change it since it is a volatile object.


if and else

  • Everyone's favorite, a powerful decision-making statement and used to control the flow of execution of statements
if (expression)
  • if allows the computer to evaluate the expression first and then, depending on whether the value of the expression (relation or condition) is true (or non-zero) or false (zero), it transfers control to a particular statement
  • else is the keyword used along with if when the mentioned expression doesn't satisfy then there should be something for the false condition of it
if (if this is true) {
    // true block statements
} else {
    // false block statements
}
  • one cannot write else without pairing if, or before if
if (i == 1) {
    printf("i is 1");
} else {
    printf("i is not 1");
}

if the value of i is other than 1, the output will be:

i is not 1
  • can be implemented in the following forms

    1. simple if statement
    2. nested if statement
    3. if ladder
    4. if ... else statement
    5. nested if ... else statement
    6. if ... else ladder
  • De Morgan's rule is applicable to if expressions

if (!x)     // if x is false then execute the statement
    true

switch, case, and default

the switch and case statement is used when a block of statements has to be executed among many blocks.

  • Sometimes complexity of if ladder increases dramatically when the number of alternatives increases, in terms of following and reading the program. Thus, the switch statement makes it more accessible.
  • switch statement tests the value of a given variable (or expression) against a list of case values and when a match is found, a block of statements associated with that case is executed
switch (expression) {
    case value1:
        // statement block 1
        break;
    case value2:
        // statement block 2
        break;
    ...
    ...
    default:
        // default statement block
        break;
}
  • The expression must be an integer or characters, whereas value1, value2, ... are constants or constant expressions known as case labels (must be unique)
  • The default is optional, when present, it will be executed if the value of the expression does not match with any of the case values.
switch (expression) {
    case '1':
        // some statements to execute when 1
        break;
    case `5`:
        // some statements to execute when 5
        break;
    default:
        // some statements to execute when default
}
  • expression must be an integral type
  • break statement transfers the control out of the switch block
  • nesting of switch is permitted

break, goto, and continue

  • break and goto keywords are used for jumping out of a loop, accomplishing an early exit
  • Whereas continue keyword is used for skipping a part of a loop
while (1) { // infinite while loop
    // continue keyword will transfer the control here
    for (---) {
        if (---) {
            // breaks the current loop and executes
            // statements under the statement block
            goto situation;
        } else if (---) {
            // if the condition is true then
            // break the loop to transfer the 
            // control out of it
            break;
        } else if (---) {
            // if the condition is true then
            // skip all other remaining part of the loop
            // and transfer the control at the start of 
            // the loop again
            continue;
        }

        // continue will skip all these statements in the loop
        ---
        ---
    }
    // break keyword will transfer the control here
}

// goto keyword will transfer the control here
situation:
    // statements
  • when the break statement is encountered inside a loop, the loop is immediately exited and the program continues with the statement immediately following the loop
  • break only exits from one loop
  • goto requires a label in order to identify the place where the branch is to be made
  • unlike break which causes the loop to be terminated, the continue, causes the loop to be continued with the next iteration after skipping any statements in between
  • it is always good practice to avoid goto, as it affects efficiency, complications, and readability
  • one can also use the exit() function instead of break for exiting out of the loop, along with the inclusion of <stdlib.h>
  • the use of break and continue statements in any of the loops is considered unstructured programming
for (int i = 1; i < 10; ++i) {
    if (i == 3)
        continue;
    if (i == 7)
        break;

    printf("%d ", i);
}

output:

1 2 4 5 6

when i is equal to 3, the continue statement comes into effect and skips 3. When i is equal to 7, the break statement comes into effect and terminates the for loop


while

  • simplest and easy-to-understand loop statement
while (test condition) {
    // body of the loop
}
  • the while is an entry-controlled loop statement
  • the test condition is evaluated and if the condition is true, then the body of the loop is executed
  • this flow continues until land unless the test condition becomes false, and control is transferred out of the loop
  • can be used for infinite execution of a program

do

  • On some occasions, it might be necessary to execute the body of the loop before the test is performed
do {
    // body of the loop
} while (test condition);
  • on reaching the do statement, the program proceeds to evaluate the body of the loop first. At the end of the loop, the test condition in the while statement is evaluated
  • if the condition is true, the program continues to evaluate the body of the loop once again. This process continues as long as the condition is true.
  • When the condition becomes false, the loop will be terminated and the control goes to the statement that appears immediately after the while statement.
  • since the test condition is evaluated at the bottom of the loop, the do...while construct provides an exit-controlled loop, and therefore the body of the loop is always executed at least once.
do {
    printf("Input a number\n");
    number = getnum();
} while (number > 0);

for

  • entry controlled loop that provides a more concise loop control structure
for (initialization; test-condition; increment) {
    // body of the loop
}
  • initialization of the control variables is done first, using assignment statements such as i = 1 and count = 0, the variables i and count are known as loop control variables
  • the value of the control variable is tested using the test condition. the test condition is a relational expression, such as i < 10 that determines when the loop will exit.
  • if the condition is true, the loop's body is executed; otherwise, the loop is terminated and the execution continues with the statement that immediately follows the loop
  • When the loop body is executed, the control is transferred back to the for statement after evaluating the last statement in the loop.
  • now, the control variable is incremented using an assignment statement such as i = i + 1, and the new value of the control variable is again tested to see whether it satisfies the loop condition, if the condition is satisfied, the body of the loop is again executed. This process continues till the value of the control variable fails to satisfy the test condition.

additional features

  • more than one variable can be initialized
for (int p = 1, n = 0; n < 17; ++n) {
    // block statement
}
  • infinite for loop
    for (; ; ) {
        // block
    }
  • nested for loops
for (int i = 0; i < rows; ++i) {
    for (int j = 0; j < columns; ++j) {
        // block
    }
}