Variables and Data Types in C

C is a statically-typed language, meaning every variable must be declared with a specific data type before it can be used. Understanding variables and data types is fundamental to writing efficient and correct C programs.


Table of Contents

  1. What are Variables?
  2. Variable Declaration and Definition
  3. Basic Data Types
  4. Type Modifiers
  5. Storage Classes
  6. Constants and Literals
  7. Type Conversion
  8. Scope and Lifetime
  9. Best Practices

What are Variables?

A variable is a named memory location that stores a value. In C, variables have:

  • Name: Identifier used to reference the variable
  • Type: Determines the size and layout of memory
  • Value: The data stored in the variable
  • Address: Memory location where the variable is stored
int age = 25;  // name: age, type: int, value: 25, address: &age

Variable Declaration and Definition

Declaration vs Definition

Declaration: Announces the existence of a variable to the compiler

extern int count;  // declaration only, no memory allocated

Definition: Declares AND allocates memory for the variable

int count;         // definition (tentative)
int count = 10;    // definition with initialization

Rules for Variable Names

  • Must begin with a letter or underscore (_)
  • Can contain letters, digits, and underscores
  • Case-sensitive (ageAgeAGE)
  • Cannot use C keywords (e.g., int, if, while)
  • Should be meaningful (prefer student_age over sa)
// Valid names
int student_age;
float _temperature;
char firstName[20];
int counter1;
// Invalid names
int 2nd_attempt;     // cannot start with digit
float my-name;       // hyphen not allowed
char class;          // 'class' is a keyword in C++
int if;              // 'if' is a keyword

Multiple Declarations

int a, b, c;                         // multiple variables, same type
int x = 10, y = 20, z = 30;          // with initialization
float price, tax = 0.05, total;       // mixed initialization

Basic Data Types

C provides several fundamental data types:

1. Integer Types

TypeSize (bytes)*RangeFormat Specifier
char1-128 to 127 or 0 to 255%c
signed char1-128 to 127%hhd
unsigned char10 to 255%hhu
short2-32,768 to 32,767%hd
unsigned short20 to 65,535%hu
int4-2,147,483,648 to 2,147,483,647%d
unsigned int40 to 4,294,967,295%u
long4 or 8-2,147,483,648 to 2,147,483,647 (32-bit)%ld
unsigned long4 or 80 to 4,294,967,295 (32-bit)%lu
long long8-9.22×10¹⁸ to 9.22×10¹⁸%lld
unsigned long long80 to 1.84×10¹⁹%llu

* Sizes are platform-dependent; shown for typical 32/64-bit systems

#include <stdio.h>
int main() {
char grade = 'A';
signed char temperature = -15;
unsigned char small_count = 200;
short year = 2023;
unsigned short population = 50000;
int count = 1000;
unsigned int positive = 4000000000U;  // 'U' suffix for unsigned
long distance = 123456789L;           // 'L' suffix for long
long long big_number = 123456789012345LL; // 'LL' suffix
printf("char: %c (size: %zu bytes)\n", grade, sizeof(grade));
printf("int: %d (size: %zu bytes)\n", count, sizeof(int));
printf("long long: %lld (size: %zu bytes)\n", big_number, sizeof(long long));
return 0;
}

2. Floating-Point Types

TypeSize (bytes)PrecisionRangeFormat Specifier
float4~6-7 decimal digits±1.5×10⁻⁴⁵ to ±3.4×10³⁸%f
double8~15-16 decimal digits±5.0×10⁻³²⁴ to ±1.7×10³⁰⁸%lf
long double10/12/16~19-34 decimal digitsplatform-dependent%Lf
#include <stdio.h>
int main() {
float pi_float = 3.141592653589793f;      // 'f' suffix for float
double pi_double = 3.141592653589793;
long double pi_long_double = 3.141592653589793L; // 'L' suffix for long double
float scientific = 1.23e-4f;               // 0.000123 in scientific notation
printf("float: %.10f (size: %zu bytes)\n", pi_float, sizeof(float));
printf("double: %.15lf (size: %zu bytes)\n", pi_double, sizeof(double));
printf("long double: %.18Lf (size: %zu bytes)\n", pi_long_double, sizeof(long double));
printf("Scientific: %f\n", scientific);
return 0;
}

3. Void Type

void represents the absence of type. Used for:

  • Functions that return nothing
  • Functions with no parameters
  • Generic pointers
void print_message(void) {     // no parameters, no return value
printf("Hello, World!\n");
}
void* generic_ptr;              // generic pointer (can point to any type)

4. _Bool Type (C99)

Boolean type introduced in C99:

#include <stdbool.h>    // for bool, true, false
int main() {
bool is_valid = true;
bool is_finished = false;
if (is_valid) {
printf("Valid!\n");
}
return 0;
}

Type Modifiers

Type modifiers alter the behavior and range of basic types:

Size Modifiers

  • short - reduces size
  • long - increases size
  • long long - further increases size (C99)

Sign Modifiers

  • signed - can store positive and negative values (default for most types)
  • unsigned - stores only non-negative values
signed int normal = -100;        // can be negative
unsigned int only_positive = 100; // cannot be negative
short int small = 10;            // same as "short"
long int large = 100000;          // same as "long"
long long int very_large = 10000000000LL;

Combined Modifiers

unsigned short int usi = 65535;
signed long int sli = -100000L;
unsigned long long int ulli = 18446744073709551615ULL;

Storage Classes

Storage classes define the scope, lifetime, and visibility of variables:

Storage ClassKeywordScopeLifetimeInitial Value
AutomaticautoBlockFunctionGarbage
ExternalexternGlobalProgramZero
StaticstaticBlock/FileProgramZero
RegisterregisterBlockFunctionGarbage
Thread Local (C11)_Thread_localThreadThreadZero

1. Automatic Variables (auto)

Default storage class for local variables:

void function() {
auto int x = 10;      // same as "int x = 10;"
auto float y = 3.14;  // rarely used explicitly
}

2. External Variables (extern)

Declare variables defined elsewhere:

// File: global.h
extern int global_counter;  // declaration
// File: global.c
int global_counter = 0;      // definition
// File: main.c
#include "global.h"
int main() {
global_counter++;        // accessing external variable
return 0;
}

3. Static Variables (static)

Local static - retains value between function calls:

#include <stdio.h>
void counter() {
static int count = 0;    // initialized only once
count++;
printf("Called %d times\n", count);
}
int main() {
counter();  // Called 1 times
counter();  // Called 2 times
counter();  // Called 3 times
return 0;
}

Global static - limits scope to the current file:

// file1.c
static int file_only = 100;  // cannot be accessed from other files
void function() {
file_only++;
}

4. Register Variables (register)

Hint to compiler to store variable in CPU register:

void loop() {
register int i;           // hint, may be ignored
for (i = 0; i < 1000000; i++) {
// fast loop
}
}

Note: Cannot take address of register variable (&i is invalid)


Constants and Literals

Integer Constants

42          // decimal
052         // octal (leading zero)
0x2A        // hexadecimal (0x prefix)
0b101010    // binary (C23, some compilers support as extension)
42U         // unsigned
42L         // long
42LL        // long long
42UL        // unsigned long
42ULL       // unsigned long long

Floating-Point Constants

3.14159     // double
3.14159F    // float
3.14159L    // long double
2.5e-3      // scientific notation (0.0025)
.5          // 0.5

Character Constants

'A'         // character constant
'\n'        // newline
'\t'        // tab
'\''        // single quote
'\\'        // backslash
'\x41'      // hexadecimal ASCII (A)

String Constants

"Hello"                     // string literal
"Hello " "World"            // concatenated (preprocessor)
"Line 1\nLine 2"            // with escape sequences

Symbolic Constants

Using #define preprocessor:

#define PI 3.14159
#define MAX_SIZE 100
#define GREETING "Hello, World!"
int main() {
float area = PI * radius * radius;
int array[MAX_SIZE];
printf("%s\n", GREETING);
return 0;
}

Using const keyword:

const float PI = 3.14159;
const int MAX_SIZE = 100;
const char* GREETING = "Hello, World!";
int main() {
// PI = 3.14;  // ERROR: cannot modify const
return 0;
}

Enumeration constants:

enum week { MON, TUE, WED, THU, FRI, SAT, SUN };
enum status { SUCCESS = 0, ERROR = -1, TIMEOUT = 5 };
int main() {
enum week today = WED;
enum status result = SUCCESS;
return 0;
}

Type Conversion

Implicit Conversion (Automatic)

Compiler automatically converts one type to another:

int i = 10;
float f = i;           // int to float (10.0)
float a = 5.5;
int b = a;             // float to int (5, truncation)
int x = 10;
long y = x;            // int to long (10L)
int result = 5 / 2;    // integer division: 2 (truncated)
float result2 = 5 / 2; // still 2.0 (division happens first)
float result3 = 5 / 2.0; // 2.5 (floating point division)

Conversion hierarchy:

char → int → long → long long → float → double → long double

Explicit Conversion (Casting)

Programmer forces type conversion:

float f = 3.14;
int i = (int)f;              // C-style cast: 3
int a = 5, b = 2;
float result = (float)a / b; // 2.5 (float division)
// Pointer casts
void* ptr = &i;
int* int_ptr = (int*)ptr;

Scope and Lifetime

Scope Types

Block Scope - within {}:

void function() {
int x = 10;        // block scope
{
int y = 20;    // nested block scope
printf("%d %d\n", x, y);  // OK
}
// printf("%d\n", y);  // ERROR: y out of scope
}

File Scope - outside all functions:

int global = 100;              // file scope
static int file_static = 200;  // file scope, limited to this file
void func1() {
global++;
}
void func2() {
global += 10;
}

Function Scope - labels only:

void function() {
goto error;
// ...
error:
printf("Error occurred\n");
}

Lifetime

  • Automatic lifetime: Local variables (created when block entered, destroyed when block exits)
  • Static lifetime: Global and static variables (entire program execution)
  • Allocated lifetime: Dynamically allocated memory (malloc/free)
#include <stdlib.h>
int global = 10;               // static lifetime
void function() {
int auto_var = 20;         // automatic lifetime
static int static_var = 30; // static lifetime
int* heap_var = malloc(sizeof(int)); // allocated lifetime
*heap_var = 40;
// ...
free(heap_var);            // manually free
}

Type Qualifiers

const

Variable cannot be modified after initialization:

const int MAX = 100;
// MAX = 200;  // ERROR
const int* ptr1 = &MAX;        // pointer to const int
int* const ptr2 = &MAX;        // const pointer to int (dangerous if MAX is const)
const int* const ptr3 = &MAX;   // const pointer to const int

volatile

Tells compiler that variable may change unexpectedly:

volatile int status_register;   // hardware register
void wait_for_flag() {
while (!flag) {
// compiler won't optimize this loop
}
}

restrict (C99)

Optimization hint that pointer is the only reference to data:

void copy(int* restrict dest, const int* restrict src, size_t n) {
// compiler can optimize knowing no overlap
for (size_t i = 0; i < n; i++) {
dest[i] = src[i];
}
}

_Atomic (C11)

Atomic operations for thread safety:

#include <stdatomic.h>
atomic_int counter = 0;
void increment() {
atomic_fetch_add(&counter, 1);
}

sizeof Operator

sizeof returns size in bytes of a type or variable:

#include <stdio.h>
int main() {
printf("char: %zu\n", sizeof(char));
printf("short: %zu\n", sizeof(short));
printf("int: %zu\n", sizeof(int));
printf("long: %zu\n", sizeof(long));
printf("long long: %zu\n", sizeof(long long));
printf("float: %zu\n", sizeof(float));
printf("double: %zu\n", sizeof(double));
printf("pointer: %zu\n", sizeof(void*));
int array[10];
printf("array: %zu\n", sizeof(array));        // 40 (if int is 4 bytes)
printf("element: %zu\n", sizeof(array[0]));   // 4
struct Data {
int x;
char y;
};
printf("struct: %zu\n", sizeof(struct Data)); // 8 (with padding)
return 0;
}

limits.h and float.h

Standard headers provide size limits for data types:

#include <stdio.h>
#include <limits.h>
#include <float.h>
int main() {
printf("Integer ranges:\n");
printf("INT_MIN: %d\n", INT_MIN);
printf("INT_MAX: %d\n", INT_MAX);
printf("UINT_MAX: %u\n", UINT_MAX);
printf("LONG_MIN: %ld\n", LONG_MIN);
printf("LONG_MAX: %ld\n", LONG_MAX);
printf("\nFloating-point ranges:\n");
printf("FLT_MIN: %e\n", FLT_MIN);
printf("FLT_MAX: %e\n", FLT_MAX);
printf("DBL_MIN: %e\n", DBL_MIN);
printf("DBL_MAX: %e\n", DBL_MAX);
return 0;
}

Best Practices

1. Choose Appropriate Types

// Good
int age;                    // age fits in int
unsigned int count;          // count is never negative
float temperature;           // temperature can be fractional
double precise_measurement;  // needs high precision
// Avoid
short int huge_number;       // might overflow
char large_array[1000000];   // char is 1 byte, but array too large for stack

2. Initialize Variables

// Good
int counter = 0;
char* name = NULL;
float total = 0.0f;
// Bad - uninitialized
int counter;
// printf("%d", counter);  // undefined behavior

3. Use Meaningful Names

// Good
int student_count;
float average_temperature;
unsigned int bytes_received;
// Avoid
int a;
float b;
unsigned int c;

4. Understand Type Limits

#include <limits.h>
int safe_add(int a, int b) {
if ((b > 0) && (a > INT_MAX - b)) {
// would overflow
return INT_MAX;
}
if ((b < 0) && (a < INT_MIN - b)) {
// would underflow
return INT_MIN;
}
return a + b;
}

5. Be Explicit About Unsigned vs Signed

// Good
unsigned int positive_only = 100;
signed int can_be_negative = -50;
// Good for bit operations
unsigned int flags = 0x0F;

6. Use size_t for Sizes and Indices

#include <stddef.h>
size_t i;
size_t length = strlen(str);  // strlen returns size_t
for (i = 0; i < length; i++) {
// process
}

7. Prefer int for General Use

// Good for most cases
int index;
int count;
int result;

8. Be Careful with Type Conversions

// Explicit cast when narrowing
double pi = 3.14159;
int approx_pi = (int)pi;  // explicit truncation
// Avoid implicit narrowing
long big = 1000000L;
int small = (int)big;  // explicit, shows you know it might overflow

Common Pitfalls

1. Integer Overflow

#include <limits.h>
int main() {
int max = INT_MAX;
int result = max + 1;        // overflow, undefined behavior
printf("%d\n", result);       // unpredictable
unsigned int umax = UINT_MAX;
unsigned int uresult = umax + 1;  // wraps to 0 (defined behavior)
printf("%u\n", uresult);      // 0
}

2. Signed/Unsigned Mismatch

int main() {
int a = -1;
unsigned int b = 1;
if (a < b) {                  // WARNING: a converted to unsigned
printf("This might not print\n");
}
printf("%u\n", a);             // prints large positive number
}

3. Float Precision Issues

#include <math.h>
#include <stdio.h>
int main() {
float a = 0.1f;
float b = 0.2f;
float c = 0.3f;
if (a + b == c) {              // likely false due to precision
printf("Equal\n");
}
// Better
if (fabs((a + b) - c) < 1e-6) {
printf("Approximately equal\n");
}
}

4. Uninitialized Pointers

int* ptr;           // uninitialized, points to random location
*ptr = 10;          // BUG: undefined behavior
// Correct
int value;
int* ptr = &value;  // initialize with valid address
*ptr = 10;

Quick Reference Table

TypeFormat SpecifierExample
char%cchar c = 'A';
signed char%hhdsigned char sc = -10;
unsigned char%hhuunsigned char uc = 200;
short%hdshort s = 1000;
unsigned short%huunsigned short us = 60000;
int%dint i = 100000;
unsigned int%uunsigned int ui = 4000000000U;
long%ldlong l = 1000000L;
unsigned long%luunsigned long ul = 4000000000UL;
long long%lldlong long ll = 10000000000LL;
unsigned long long%lluunsigned long long ull = 10000000000ULL;
float%ffloat f = 3.14f;
double%lfdouble d = 3.14159;
long double%Lflong double ld = 3.141592653589793238L;
size_t%zusize_t sz = sizeof(int);
ptrdiff_t%tdptrdiff_t diff = ptr2 - ptr1;
void*%pvoid* ptr = &i;

Conclusion

Understanding variables and data types is essential for C programming. Key takeaways:

  • C is statically typed - every variable has a fixed type
  • Choose appropriate types for your data (int for counting, float/double for real numbers)
  • Be aware of type sizes and limits (use <limits.h> and <float.h>)
  • Understand scope and lifetime of variables
  • Initialize variables before use
  • Be careful with type conversions, especially signed/unsigned mixing
  • Use const for values that shouldn't change
  • Consider storage classes for controlling visibility and lifetime

Proper use of variables and data types leads to efficient, portable, and bug-free C programs.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper