Variables and Data Types in C
Introduction
Welcome to Variables and Data Types! If a C program is a factory processing materials, data types dictate the shape and size of the containers, and variables are the labels slapped onto those containers.
Imagine you are packing for a trip. You wouldn't use a massive wardrobe box to pack a single toothbrush, nor would you try to fit a bicycle into a shoebox. You choose a container that fits the item. In C, memory is a finite resource. By specifying data types, you tell the compiler exactly what kind of container you need. An integer (int) requires a different sized container than a single character (char) or a decimal number (float).
C is a statically typed language. This means you must explicitly declare the container's size (the type) before you can put anything inside it, and once declared, you cannot change the type of that variable.
Core Concept: First Principles
At the hardware level, all data is stored as binary bits (0s and 1s). The C compiler needs to know two critical things about any variable to generate correct machine code:
- Size: How many bytes of memory should be reserved?
- Encoding: How should the binary bits in those bytes be interpreted? (e.g., Is
01000001the number 65, or the letter 'A'?)
Here are the primitive data types in C:
char: Stores a single character. It is guaranteed to be exactly 1 byte (8 bits) in size. Characters are mapped to numbers using the ASCII encoding standard.int: Stores a whole number (integer). The size of anintdepends on the system architecture but is almost universally 4 bytes (32 bits) on modern systems, allowing values from roughly -2.14 billion to +2.14 billion.float: Stores a single-precision floating-point number (decimal). Usually 4 bytes. It provides about 6-7 decimal digits of precision.double: Stores a double-precision floating-point number. Usually 8 bytes. It provides about 15 decimal digits of precision and is preferred overfloatwhen high accuracy is needed.
Modifiers: C provides modifiers to alter the capacity of these types:
unsigned: Removes negative numbers, doubling the positive upper limit. (e.g.,unsigned intgoes from 0 to 4.29 billion).short: Halves the size of anint(usually 2 bytes).long: Increases the size (often 8 bytes on 64-bit Linux/Mac, but remains 4 bytes on Windows).
Beginner Code Example
Here is how you declare, initialize, and print various data types. Notice how printf uses different format specifiers for different types.
#include <stdio.h>
int main() {
// Declaration and Initialization
char grade = 'A'; // Use single quotes for chars
int age = 21; // Whole numbers
float gpa = 3.85f; // The 'f' suffix specifies it's a float
double exact_pi = 3.1415926535; // Doubles don't need a suffix
// Printing variables using format specifiers
// %c for char, %d for int, %f for float, %lf for double
printf("Student Profile:\n");
printf("Age: %d\n", age);
printf("Grade: %c\n", grade);
// %.2f restricts the output to 2 decimal places
printf("GPA: %.2f\n", gpa);
printf("Pi approximation: %lf\n", exact_pi);
return 0;
}
Intermediate Code Example: Memory Sizes
Because C interacts closely with hardware, it is vital to know exactly how much memory your variables consume. C provides the sizeof operator, which returns the size of a type or variable in bytes.
#include <stdio.h>
int main() {
char c;
short s;
int i;
long l;
float f;
double d;
printf("Memory footprints on this system:\n");
// %zu is the format specifier for the type returned by sizeof (size_t)
printf("Size of char: %zu byte(s)\n", sizeof(c));
printf("Size of short: %zu byte(s)\n", sizeof(s));
printf("Size of int: %zu byte(s)\n", sizeof(i));
printf("Size of long: %zu byte(s)\n", sizeof(l));
printf("Size of float: %zu byte(s)\n", sizeof(f));
printf("Size of double: %zu byte(s)\n", sizeof(d));
// Demonstrating ASCII underlying char
char letter = 'a';
printf("\nThe character '%c' has the underlying integer value: %d\n", letter, letter);
// We can do math on chars because they are just small integers!
letter = letter - 32; // Converts lowercase 'a' to uppercase 'A'
printf("After subtracting 32: %c\n", letter);
return 0;
}
Advanced Code Example: Overflow and Precision
Data containers have limits. If you try to stuff a number larger than the container can hold, it "overflows" and wraps around to the lowest possible negative number.
Floating-point numbers also suffer from precision loss because some decimals cannot be represented perfectly in binary.
#include <stdio.h>
#include <limits.h> // Contains min/max macros for integer types
int main() {
// --- INTEGER OVERFLOW ---
// INT_MAX is the largest possible standard integer (2,147,483,647)
int max_int = INT_MAX;
printf("Max int value: %d\n", max_int);
// Adding 1 causes integer overflow!
max_int = max_int + 1;
printf("Max int + 1 : %d (Overflow!)\n", max_int);
// --- FLOATING POINT PRECISION ---
float price = 0.1f;
float sum = 0.0f;
// Add 0.1 ten times. Mathematically, 0.1 * 10 = 1.0.
for(int i = 0; i < 10; i++) {
sum += price;
}
printf("\nExpected sum: 1.000000\n");
// We print 7 decimal places to expose the binary rounding error
printf("Actual sum : %.7f\n", sum);
if (sum == 1.0f) {
printf("Equal!\n");
} else {
printf("Not equal! Never use == to compare floats.\n");
}
return 0;
}
Common Mistakes
1. Using Uninitialized Variables
If you declare a variable but do not assign it a value, it contains "garbage" (whatever binary data happened to be left in that memory location). Wrong:
int score;
printf("%d", score); // Prints unpredictable garbage number
Correct:
int score = 0;
printf("%d", score); // Prints 0
2. Format Specifier Mismatches
Using %d to print a float or %f to print an int will result in bizarre, incorrect output because printf is interpreting the bits incorrectly.
Wrong:
float pi = 3.14;
printf("%d", pi); // Prints a random massive number or 0
Correct:
float pi = 3.14;
printf("%f", pi);
3. Integer Division Truncation
When you divide an integer by an integer in C, the result is always an integer. The decimal part is thrown away, not rounded. Wrong:
float result = 5 / 2;
// 5 and 2 are ints. 5/2 is 2. 'result' becomes 2.0.
Correct:
float result = 5.0 / 2.0;
// At least one number must be a float to force floating-point division.
Interview Q&A
Q1: What is the difference between statically typed and dynamically typed languages? A: In a statically typed language (like C), the type of a variable is checked at compile-time and cannot change. In a dynamically typed language (like Python), the type is determined at runtime, and a variable can hold an integer, then later hold a string.
Q2: What is the purpose of the unsigned keyword?
A: The unsigned keyword specifies that an integer type can only hold non-negative values (zero and positive numbers). By dropping support for negative numbers, it uses the sign bit to double the maximum positive value the variable can store.
Q3: How does the compiler know the size of an int?
A: The C standard does not strictly define exact byte sizes for types (except char, which is always 1 byte). It only defines minimum ranges. The actual size of an int is determined by the compiler, optimized for the target processor architecture (usually 4 bytes on modern 32-bit and 64-bit systems).
Q4: Why should you avoid comparing floating-point numbers with ==?
A: Because floating-point numbers are represented in binary scientific notation, certain decimal fractions (like 0.1) cannot be represented exactly. This leads to minuscule rounding errors. Instead of a == b, you should check if the absolute difference between them is smaller than a tiny threshold (an epsilon).
Q5: What is integer overflow? A: Integer overflow occurs when an arithmetic operation attempts to create a numeric value that is larger than the maximum value the data type can represent. In C, signed integer overflow results in undefined behavior (though practically, it usually wraps around to the lowest negative number).
Practice Problems
-
Size Checker: Write a program that asks the user to input a single character. Print the character, its ASCII integer equivalent, and the memory size of a
charvariable usingsizeof. -
Temperature Converter: Write a program that converts degrees Fahrenheit to Celsius. Declare
fahrenheitas afloatand initialize it to 98.6. The formula isC = (F - 32) * 5 / 9. Print the result to 2 decimal places. Beware of integer division! -
The Wrapper: Write a program that declares an
unsigned char(which holds 0 to 255). Initialize it to 250. Write a loop that adds 1 to it 10 times, printing the value on each iteration. Observe what happens when it crosses 255.
Summary
- C is statically typed: you must define a variable's data type before using it.
charis for single characters (1 byte, relies on ASCII).intis for whole numbers (usually 4 bytes).floatanddoubleare for decimal numbers (4 and 8 bytes respectively).- Use
sizeof()to determine the exact memory footprint of a type on your system. - Be cautious of integer division truncating decimals.
- Be aware of minimum and maximum limits to avoid integer overflow.
- Floating-point numbers can suffer from binary precision issues.