Understanding C Data Types and Variables
Data Types in C
C provides a rich set of data types to represent various kinds of information. Integer types handle whole numbers, character types handle individual characters, and floating-point types handle decimal values. A "type" defines the common characteristics of related data, and the compiler needs to know the type to determine how to manipulate the data.
Let's examine all the data types C has to offer.
Determining Type Sizes
The sizeof operator is a keyword that calculates the memory size of a type, expression, or varible in bytes. It's evaluated at compile time rather than runtime.
Key points about sizeof:
- The expression inside
sizeofis not actually evaluated; only its type determines the result - The return type of
sizeofissize_t - On different systems,
size_tmay beunsigned intorunsigned long long
#include <stdio.h>
int main()
{
printf("%zd\n", sizeof(char));
printf("%zd\n", sizeof(_Bool));
printf("%zd\n", sizeof(short));
printf("%zd\n", sizeof(int));
printf("%zd\n", sizeof(long));
printf("%zd\n", sizeof(long long));
printf("%zd\n", sizeof(float));
printf("%zd\n", sizeof(double));
printf("%zd\n", sizeof(long double));
return 0;
}
Typical output on a 64-bit system:
char: 1_Bool: 1short: 2int: 4long: 4long long: 8float: 4double: 8long double: 8
Expression Evaluation in sizeof
#include <stdio.h>
int main()
{
short budget = 2;
int counter = 10;
printf("%d\n", sizeof(budget = counter + 1));
printf("budget = %d\n", budget);
return 0;
}
The expression budget = counter + 1 never executes. During compilation, sizeof determines the result based on the type of budget, which is short. The actual assignment happens at runtime, but sizeof is already resolved during compilation, so the variable remains unchanged.
Variables
Variable Scope
Variables declared outside any code block are global variables. They remain accessible throughout the entire program and can be used by any function.
Variables declared inside a code block (within braces) are local variables. Their visibility is limited to the enclosing block where they're defined.
When a local variable shares the same name as a global variable, the local variable takes precedance within its scope.
#include <stdio.h>
int global_count = 1000;
int main()
{
int global_count = 10;
printf("%d\n", global_count); // outputs 10
return 0;
}
In this example, the local global_count shadows the global variable when accessed inside main().
Memory Storage Locations
C organizes memory into three main regions:
| Variable Type | Memory Region |
|---|---|
| Local variables | Stack |
| Global variables | Static/bss |
| Dynamically allocated | Heap |
Local variables reside on the stack, global variables live in the static region, and the heap manages dynamically allocated memory through functions we'll cover later.
Input and Output Functions
printf
The printf() function outputs formatted text to the console. The "f" stands for "format," indicating its ability to format output with placeholders.
Format Specifiers
Format specifiers begin with % and indicate how values should be displayed:
| Specifier | Description |
|---|---|
%a |
Hexadecimal floating-point (lowercase) |
%A |
Hexadecimal floating-point (uppercase) |
%c |
Character |
%d |
Decimal integer (int) |
%e |
Scientific notation (lowercase exponent) |
%E |
Scientific notation (uppercase exponent) |
%i |
Integer (equivalent to %d) |
%f |
Floating-point (float uses %f, double uses %lf) |
%g |
Compact floating-point, up to 6 significant digits |
%G |
Same as %g with uppercase E |
%hd |
Short int in decimal |
%ho |
Short int in octal |
%hx |
Short int in hexadecimal |
%hu |
Unsigned short int |
%ld |
Long int in decimal |
%lo |
Long int in octal |
%lx |
Long int in hexadecimal |
%lu |
Unsigned long int |
%lld |
Long long int in decimal |
%llo |
Long long int in octal |
%llx |
Long long int in hexadecimal |
%llu |
Unsigned long long int |
%Le |
Long double in scientific notation |
%Lf |
Long double |
%n |
Stores count of characters printed so far |
%o |
Octal integer |
%p |
Pointer address |
%s |
String |
%u |
Unsigned integer |
%x |
Hexadecimal integer |
%zd |
size_t type |
%% |
Literal percent sign |
Width Control
You can specify minimum field widths:
printf("%5d\n", 123); // outputs " 123"
This ensures the output occupies at least 5 characters. By default, values are right-aligned with leading spaces. Add a - after % for left-alignment.
For floating-point numbers, the width applies to the entire output:
printf("%12f\n", 123.45); // outputs " 123.450000" (2 leading spaces)
Since printf displays 6 decimal places by default for floats, 123.45 becomes 123.450000, requiring 2 leading spaces to reach 12 characters.
Precision Control
Control decimal places with .N notation:
printf("Result: %.2f\n", 0.5); // outputs "Result: 0.50"
printf("Result: %.3f\n", 0.5); // outputs "Result: 0.500"
scanf
The scanf() function reads formatted input from standard input. Execution pauses until the user provides input and presses Enter. The function then parses the input according to the format string and stores values in the specified variables.
The functon is declared in <stdio.h> and follows a syntax similar to printf().
scanf() automatically skips whitespace characters (spaces, tabs, newlines) when processing numeric format specifiers.
Input parsing resumes from where the previous scanf() call left off, continuing until either the buffer is exhausted or a character that doesn't match the expected format is encountered.
Consider input: -13.45e12# 0
int x;
float y;
scanf("%d", &x);
printf("%d\n", x);
scanf("%f", &y);
printf("%f\n", y);
The first scanf() with %d ignores leading whitespace, then reads from the - character and stops at . (not a valid integer character). Result: x = -13.
The second scanf() with %f continues from ., reading .45e12 as valid scientific notation (a dot followed by digits is valid for floats). The # is not a floating-point character, so parsing stops there. Result: y = 0.4512e12.
Note that scanf() requires the & operator before variable names to pass their memory addresses.