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
- What are Variables?
- Variable Declaration and Definition
- Basic Data Types
- Type Modifiers
- Storage Classes
- Constants and Literals
- Type Conversion
- Scope and Lifetime
- 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 (
ageâAgeâAGE) - Cannot use C keywords (e.g.,
int,if,while) - Should be meaningful (prefer
student_ageoversa)
// 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
| Type | Size (bytes)* | Range | Format Specifier |
|---|---|---|---|
char | 1 | -128 to 127 or 0 to 255 | %c |
signed char | 1 | -128 to 127 | %hhd |
unsigned char | 1 | 0 to 255 | %hhu |
short | 2 | -32,768 to 32,767 | %hd |
unsigned short | 2 | 0 to 65,535 | %hu |
int | 4 | -2,147,483,648 to 2,147,483,647 | %d |
unsigned int | 4 | 0 to 4,294,967,295 | %u |
long | 4 or 8 | -2,147,483,648 to 2,147,483,647 (32-bit) | %ld |
unsigned long | 4 or 8 | 0 to 4,294,967,295 (32-bit) | %lu |
long long | 8 | -9.22Ă10Âčâž to 9.22Ă10Âčâž | %lld |
unsigned long long | 8 | 0 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
| Type | Size (bytes) | Precision | Range | Format Specifier |
|---|---|---|---|---|
float | 4 | ~6-7 decimal digits | ±1.5Ă10â»âŽâ” to ±3.4Ă10Âłâž | %f |
double | 8 | ~15-16 decimal digits | ±5.0Ă10â»ÂłÂČ⎠to ±1.7Ă10Âłâ°âž | %lf |
long double | 10/12/16 | ~19-34 decimal digits | platform-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 sizelong- increases sizelong 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 Class | Keyword | Scope | Lifetime | Initial Value |
|---|---|---|---|---|
| Automatic | auto | Block | Function | Garbage |
| External | extern | Global | Program | Zero |
| Static | static | Block/File | Program | Zero |
| Register | register | Block | Function | Garbage |
| Thread Local (C11) | _Thread_local | Thread | Thread | Zero |
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
| Type | Format Specifier | Example |
|---|---|---|
char | %c | char c = 'A'; |
signed char | %hhd | signed char sc = -10; |
unsigned char | %hhu | unsigned char uc = 200; |
short | %hd | short s = 1000; |
unsigned short | %hu | unsigned short us = 60000; |
int | %d | int i = 100000; |
unsigned int | %u | unsigned int ui = 4000000000U; |
long | %ld | long l = 1000000L; |
unsigned long | %lu | unsigned long ul = 4000000000UL; |
long long | %lld | long long ll = 10000000000LL; |
unsigned long long | %llu | unsigned long long ull = 10000000000ULL; |
float | %f | float f = 3.14f; |
double | %lf | double d = 3.14159; |
long double | %Lf | long double ld = 3.141592653589793238L; |
size_t | %zu | size_t sz = sizeof(int); |
ptrdiff_t | %td | ptrdiff_t diff = ptr2 - ptr1; |
void* | %p | void* 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
constfor 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.
Complete C Programming Guide + Compilers Collection
1. C srand() Function â Understanding Seed Initialization
https://macronepal.com/understanding-the-c-srand-function
Explains how srand() initializes the pseudo-random number generator in C by setting a seed value. Using the same seed produces the same sequence, while time(NULL) gives different results each run.
2. C rand() Function Mechanics and Limitations
https://macronepal.com/c-rand-function-mechanics-and-limitations
Explains how rand() generates pseudo-random numbers between 0 and RAND_MAX, its deterministic nature, and limitations for security use cases.
3. C log() Function
https://macronepal.com/c-log-function-2
Covers natural logarithm calculation using <math.h> and its applications.
4. Mastering Date and Time in C
https://macronepal.com/mastering-date-and-time-in-c
Explains <time.h> functions like time(), clock(), difftime(), and struct tm.
5. Mastering time_t Type in C
https://macronepal.com/mastering-the-c-time_t-type-for-time-management
Explains time representation as seconds since Unix epoch and conversion functions.
6. C exp() Function
https://macronepal.com/c-exp-function-mechanics-and-implementation
Explains exponential function exp(x) and its scientific applications.
7. C log() Function (Alternate Guide)
https://macronepal.com/c-log-function
Comparison of log() and log10() with usage examples.
8. C log10() Function
https://macronepal.com/mastering-the-log10-function-in-c
Explains base-10 logarithm for engineering and scientific applications.
9. C tan() Function
https://macronepal.com/understanding-the-c-tan-function
Explains tangent function and radian-based calculations.
10. Random Numbers in C (Secure vs Predictable)
https://macronepal.com/mastering-c-random-numbers-for-secure-and-predictable-applications
Explains difference between rand() and secure randomness methods.
11. Free Online C Compiler
https://macronepal.com/free-online-c-code-compiler-2
Browser-based compiler for testing C programs instantly.
C Functions, Arguments, Parameters & Flow
Mastering Functions in C â Complete Guide
https://macronepal.com/c/mastering-functions-in-c-a-complete-guide/
Covers function structure, modular programming, and real-world usage.
Function Arguments in C
https://macronepal.com/c-function-arguments/
Explains how arguments are passed and used in function calls.
Function Parameters in C
https://macronepal.com/c-function-parameters/
Explains defining inputs for functions and matching them with arguments.
Function Declarations in C
https://macronepal.com/c-function-declarations-syntax-rules-and-best-practices/
Covers prototypes, syntax rules, and best practices.
Function Calls in C
https://macronepal.com/understanding-function-calls-in-c-syntax-mechanics-and-best-practices/
Explains execution flow and parameter handling during function calls.
Void Functions in C
https://macronepal.com/understanding-void-functions-in-c-syntax-patterns-and-best-practices/
Explains functions that do not return values.
Return Values in C
https://macronepal.com/c-return-values-mechanics-types-and-best-practices/
Explains different return types and how functions return results.
Pass-by-Value in C
https://macronepal.com/aws/understanding-pass-by-value-in-c-mechanics-implications-and-best-practices/
Explains how copies of variables are passed into functions.
Pass-by-Reference in C
https://macronepal.com/c/understanding-pass-by-reference-in-c-pointers-semantics-and-safe-practices/
Explains using pointers to modify original variables.
C strstr() Function
https://macronepal.com/aws/c-strstr-function/
Explains substring search inside strings in C.
C Preprocessor & Macros
https://macronepal.com/mastering-c-variadic-macros-for-flexible-debugging/
https://macronepal.com/mastering-the-stdc-macro-in-c/
https://macronepal.com/c-time-macro-mechanics-and-usage/
https://macronepal.com/understanding-the-c-date-macro/
https://macronepal.com/c-file-type/
https://macronepal.com/mastering-c-line-macro-for-debugging-and-diagnostics/
https://macronepal.com/mastering-predefined-macros-in-c/
https://macronepal.com/c-error-directive-mechanics-and-usage/
https://macronepal.com/understanding-the-c-pragma-directive/
https://macronepal.com/c-include-directive/
C Structures, Memory, Scope & Linkage
https://macronepal.com/mastering-structures-in-c/
https://macronepal.com/c-structure-declaration-mechanics-and-usage/
https://macronepal.com/c-structure-initialization-mechanics-and-best-practices/
https://macronepal.com/mastering-c-structure-member-access-for-reliable-data-handling/
https://macronepal.com/c-nested-structures/
https://macronepal.com/mastering-arrays-of-structures-in-c/
https://macronepal.com/c-structure-pointers-mechanics-and-implementation/
https://macronepal.com/understanding-c-structure-parameter-passing-mechanics/
https://macronepal.com/mastering-c-returning-structures-for-efficient-data-flow/
https://macronepal.com/c-self-referential-structures/
https://macronepal.com/mastering-structure-alignment-in-c/
https://macronepal.com/c-structure-padding-mechanics-and-optimization/
https://macronepal.com/understanding-c-flexible-array-members-mechanics-and-usage/
https://macronepal.com/mastering-c-anonymous-structures-for-flattened-data-layouts/
https://macronepal.com/c-unions/
https://macronepal.com/mastering-c-name-mangling-and-symbol-decoration/
https://macronepal.com/c-no-linkage-mechanics-and-scope-isolation/
https://macronepal.com/understanding-c-internal-linkage-mechanics-and-architecture/
C Scope, Storage Classes & Typedef
https://macronepal.com/mastering-function-prototype-scope-in-c/
https://macronepal.com/c-function-scope-mechanics-and-visibility/
https://macronepal.com/understanding-c-file-scope-mechanics-and-architecture/
https://macronepal.com/mastering-c-scope-rules-for-predictable-name-resolution/
https://macronepal.com/c-scope-rules/
https://macronepal.com/mastering-c-register-storage-class-for-historical-context-and-modern-alternatives/
https://macronepal.com/mastering-_thread_local-in-c/
https://macronepal.com/c-extern-storage-class-mechanics-and-usage/
https://macronepal.com/understanding-the-c-static-storage-class-mechanics-and-usage/
https://macronepal.com/c-auto-storage-class/
https://macronepal.com/c-typedef-with-pointers/
Extra Articles
https://macronepal.com/13757-2/
https://macronepal.com/13748-2/
https://macronepal.com/13747-2/
https://macronepal.com/13746-2/
https://macronepal.com/13745-2/
https://macronepal.com/13708-2/
https://macronepal.com/13707-2/
https://macronepal.com/13702-2/
Online Compilers
https://macronepal.com/free-html-online-code-compiler/
https://macronepal.com/free-online-python-code-compiler/
https://macronepal.com/free-online-python2-code-compiler/
https://macronepal.com/free-online-java-code-compiler/
https://macronepal.com/free-online-javascript-code-compiler/
https://macronepal.com/free-online-node-js-code-compiler/
https://macronepal.com/free-online-c-code-compiler/
https://macronepal.com/free-online-c-code-compiler-2/
https://macronepal.com/free-online-c-code-compiler-3/
https://macronepal.com/free-online-php-code-compiler/
https://macronepal.com/free-online-ruby-code-compiler/
https://macronepal.com/free-online-perl-code-compiler/
https://macronepal.com/free-online-lua-code-compiler/
https://macronepal.com/free-online-tcl-code-compiler/
https://macronepal.com/free-online-groovy-code-compiler/
https://macronepal.com/free-online-j-shell-code-compiler/
https://macronepal.com/free-online-haskell-code-compiler/
https://macronepal.com/free-online-scala-code-compiler/
https://macronepal.com/free-online-common-lisp-code-compiler/
https://macronepal.com/free-online-d-code-compiler/
https://macronepal.com/free-online-ada-code-compiler/
https://macronepal.com/free-erlang-code-compiler/
https://macronepal.com/free-online-assembly-code-compiler/
