Variables – C

Variables are simply put labels, which can be used by programmer to store, read and modify data in memory. Because C is strongly typed language each variable has a specific type which determines which data can be stored and how much memory will it occupy.

In C language variables have a type (See Data types) and identifier (name / label). In addition to that it might have some modifiers preceeding it, specifying the size, signedness, memory attributes, this will be discussed further down this post.

Declaring a variable

Before one can start storing data in a variable, it has to be declared first. This is essentially telling : ‘I need to have “this big” container, be ready to keep my precious data here (while having a label on the container, telling you what kind of stuff will be kept there)‘.

To declare a variable one must specify data type and label e.g.int age;.  This will allocate 4 bytes to your program which you as a programmer can use to store integer values. This memory will be kept for your program throughout the scope of particular code where your variable is stored.  Few things to note when declaring a variable:

  • You will get seemingly random memory space to keep your data
    • Variable might contain garbage data (remnants of previous programs)
      • You are not guaranteed to have 0 as initial value (Initialize to fix)
    • You might not get the same memory space for variables each time you execute your program
  • You need to specify the type only when a variable is declared
    • Specifying data type is only required once – when declaring a variable, after that use only label
  • You can declare same named variable only once within one scope
    • Working example:
      for (int i = 0; i < 10; i++) {
          printf("%d\n", i);
      }
      
      for (int i = 10; i > 0; i--) {
          printf("%d\n", i);
      }
    • Erroneous example (Redeclared variable):
      int i = 1000;
      
      for (int i = 10; i > 0; i--) {
          printf("%d\n", i);
      }

Examples of declaring variables (in this example variables are not initialized):

// Declaring multiple variables with multiple statements
int age;
int counter;
int length;
double result;

// Declaring multiple variables with one statement (Works only for variables of same type)
int wisdom,
    dexterity,
    constitution,
    intelligence,
    strength,
    charisma;

Operations with variables

Initializing a variable

Initializing a variable is giving it an initial value which will fix the problem of possibly having garbage data inside your variable. This is to avoid undefined behavior in your program (no need to play Bingo to get expected results). This can be done using assignment operator after variable identifier and then specifying a value. Example of initializing : int lowest_acceptable_grade = 5;. Even though it is not mandatory to initialize your variables its highly recommended!

Examples of how to initialize different variable types:

char ci = 'a';
unsigned char cu = 233;
short si = -3232;
unsigned short su = 55555;
int ii = -23232353;
unsigned int iu = 1000000;
long li = -10101010;
unsigned long lu = 101010101;
long long lli = -123123123;
unsigned long long llu = 321321321;
float f = 3.14;
double lf = -8.1337;
long double llf = 0.5318008;
void *random_data = (void *)0x7887ff01ff4860;

Assigning a value

Assigning a value is done the same way as initialization at declaration – using an assignment operator.

Example:

int age;    // Declaration
age = 30;   // Assigning a value

Typecasting

Typecasting is saying that a preexisting variable should be handled as a different type. the syntax for typecasting is: (type)identifier;, where identifier is variable name / label.

Typecasting is most often done when dealing with arithmetic operations (1) and with pointers (2).

  1. int x = 256;
    int y = 7;
    double result;
    result = x / y;                 // 36
    result = (double)x / y;         // 36.571429
    result = x / (double)y;         // 36.571429
    result = (double)x / (double)y; // 36.571429

    NB! Dividing integers with each other will truncate the real part!

  2. // Example compare function for qsort
    int compare(void *a, void *b) {
        return (*(int *)a - *(int *)b);
    }

When typecasting the casted type will have the properties of type to which it is casted, this might create surprises to you – meaning unexpected results. There is no reason why to typecast to the same type as the variable is even though it is syntactically correct.

Variables used in the following typecast examples:

struct new_type {
    unsigned char a;
    unsigned char b;
    unsigned char c;
    unsigned char d;
};
struct new_type structure = {
    .a = 22,                    // 0x16
    .b = -5,                    // 0xfb
    .c = 0,                     // 0x00
    .d = 127                    // 0x7f
};
char small = -25;
int number = 1697;
double real = 3.14;
int output = 0;
double real_output = 0.0;

Memory representation of different variable types:

// Representation of 4 byte variable (int, new_type):
// | byte 3 | byte 2 | byte 1 | byte 0 |
// Representation of 1 byte variable (char):
//                            | byte 0 |

 

  1. Safe typecasting (casted type is same size or bigger than the type of variable to be casted)
    output = (int)small;            // -25
    output = (int)number;           // 1697
    real_output = (double)number;   // 1697.0
  2. Typecasting will truncate the bytes that exceed the size of casted type (data loss possible)
    output = (char)number;          // -95
    real_output = (float)real;      // 3.14 - only loses precision
    // number in memory:
    // | 0x00 | 0x00 | 0x06 | 0xa1 |
    // output in memory:
    // | 0x00 | 0x00 | 0x00 | 0xa1 |
  3. Typecasting and forgetting about sign bit
    output = (unsigned char)small;  // 231
  4. Typecasting to data type which has different representation of data (See memory / pointers post to understand what’s going on on the right side of assignment operand)

    output = *((int *)(&structure));// 2130770710
    // structure in memory (equivalent to integer: 213077010 in decimal):
    // | 0x7f | 0x00 | 0xfb | 0x16 |