Core Components of a C Program
A typical C application comprises preprocessor directives, functions, variables, expressions, and comments.
Data Types
The memory footprint of data types varies depending on the architecture (e.g., 32-bit vs 64-bit systems).
Integer Types
| Type | Size | Value Range |
|---|---|---|
| char | 1 byte | -128 to 127 or 0 to 255 |
| unsigned char | 1 byte | 0 to 255 |
| signed char | 1 byte | -128 to 127 |
| int | 2 or 4 bytes | -32,768 to 32,767 or -2,147,483,648 to 2,147,483,647 |
| unsigned int | 2 or 4 bytes | 0 to 65,535 or 0 to 4,294,967,295 |
| short | 2 bytes | -32,768 to 32,767 |
| unsigned short | 2 bytes | 0 to 65,535 |
| long | 4 bytes | -2,147,483,648 to 2,147,483,647 |
| unsigned long | 4 bytes | 0 to 4,294,967,295 |
Floating-Point Types
| Type | Size | Value Range | Precision |
|---|---|---|---|
| float | 4 bytes | 1.2E-38 to 3.4E+38 | 6 decimal places |
| double | 8 bytes | 2.3E-308 to 1.7E+308 | 15 decimal places |
| long double | 16 bytes | 3.4E-4932 to 1.1E+4932 | 19 decimal places |
Void Type
| Usage | Description |
|---|---|
| Function returns void | Indicates a function does not return a value. Example: void exit_app(int status); |
| Function parameters void | Signifies a function takes no arguments. Example: int get_rand(void); |
| Void pointers | Represents a pointer to an unknown type. Memory functions like void *malloc(size_t bytes); return generic pointers that can be cast to any type. |
Lvalues and Rvalues
Expressions in C are categorized into two types:
- Lvalue: An expression referring to a memory address. Lvalues can appear on either side of an assignment operator.
- Rvalue: An expression representing a data value stored at some address. Rvalues cannot be assigned to and must appear only on the right side of an assignment.
Variables act as lvalues, while numeric literals act as rvalues. Valid statement:
int age = 42;
Invalid statement (compilation error):
10 = 42;
Preprocessor Macros vs Constants
Both #define and const create constant values, but const is generally preferred for type safety and scoping.
- Mechanism:
#defineperforms textual substitution before compilation.constdeclares a typed variable whose value is fixed at runtime or compile-time. - Type Safety:
#definelacks type checking.constvariables have explicit types, enabling the compiler to catch mismatched operations. - Scope: Macros defined with
#definepersist globally from their point of definition.constvariables respect block-level scoping rules. - Debugging: Macros are invisible in the debugger's symbol table.
constvariables retain their names in the symbol table, easing debugging.
Storage Classes
Storage classes dictate the visibility, memory location, and lifespan of variables.
autoregisterstaticextern
auto
The default storage class for local variables. They are allocated upon entering a block and deallocated upon exiting.
{
int idx;
auto int count;
}
register
Advises the compiler to store the variable in a processor register rather than RAM for rapid access. You cannot use the address-of operator (&) on register variables.
{
register int fast_access_var;
}
The compiler may ignore this hint if hardware constraints prevent it.
static
Tells the compiler to maintain the variable's existence throughout the program's execution rather than recreating it on each scope entry. For global variables, static restricts visibility to the defining translation unit.
#include <stdio.h>
void increment_counter(void);
static int loop_limit = 3;
int main() {
while (loop_limit--) {
increment_counter();
}
return 0;
}
void increment_counter(void) {
static int persistent_val = 10;
persistent_val += 5;
printf("Persistent: %d, Limit: %d\n", persistent_val, loop_limit);
}
Output:
Persistent: 15, Limit: 2
Persistent: 20, Limit: 1
Persistent: 25, Limit: 0
extern
Used to declare a variable or function defined in another source file. It does not allocate new memory but references an existing entity.
File: main_app.c
#include <stdio.h>
int shared_data;
extern void print_shared();
int main() {
shared_data = 100;
print_shared();
return 0;
}
File: helper_func.c
#include <stdio.h>
extern int shared_data;
void print_shared(void) {
printf("Shared value: %d\n", shared_data);
}
Compile with gcc main_app.c helper_func.c. Running the program outputs: Shared value: 100
Operators
Arithmetic Operators
Increment operators have distinct behaviors based on prefix/suffix placement:
var++: Returns current value, then increments.++var: Increments value, then returns new value.
Logical Operators
| Operator | Description | Example |
|---|---|---|
| && | Logical AND. True if both operands are non-zero. | (X && Y) is false |
| || | Logical OR. True if any operand is non-zero. | (X || Y) is true |
| ! | Logical NOT. Reverses the logical state of the operand. | !(X && Y) is true |
Bitwise Operators
Operate on individual bits. Let X = 12 (0000 1100) and Y = 10 (0000 1010):
| Operator | Description | Result |
|---|---|---|
| & | Bitwise AND: Sets bit to 1 if both bits are 1. | X & Y = 8 (0000 1000) |
| | | Bitwise OR: Sets bit to 1 if either bit is 1. | X | Y = 14 (0000 1110) |
| ^ | Bitwise XOR: Sets bit to 1 if bits are different. | X ^ Y = 6 (0000 0110) |
| ~ | Bitwise NOT: Inverts all bits. | ~X = -13 (1111 0011) |
| << | Left Shift: Moves bits left, filling with 0s. Equivalent to multiplying by 2^n. | X << 2 = 48 (0011 0000) |
| >> | Right Shift: Moves bits right. Equivalent to dividing by 2^n. | X >> 2 = 3 (0000 0011) |
Assignment Operators
| Operator | Description | Equivalent |
|---|---|---|
| = | Simple assignment | Z = X + Y |
| += | Add and assign | Z += X is Z = Z + X |
| -= | Subtract and assign | Z -= X is Z = Z - X |
| *= | Multiply and assign | Z *= X is Z = Z * X |
| /= | Divide and assign | Z /= X is Z = Z / X |
| %= | Modulo and assign | Z %= X is Z = Z % X |
| <<= | Left shift and assign | Z <<= 2 is Z = Z << 2 |
| >>= | Right shift and assign | Z >>= 2 is Z = Z >> 2 |
| &= | Bitwise AND and assign | Z &= 2 is Z = Z & 2 |
| ^= | Bitwise XOR and assign | Z ^= 2 is Z = Z ^ 2 |
| |= | Bitwise OR and assign | Z |= 2 is Z = Z | 2 |
Miscellaneous Operators
| Operator | Description | Example |
|---|---|---|
| sizeof() | Yields the memory size of a variable or type. | sizeof(int) returns 4 |
| & | Yields the memory address of a variable. | &val |
| * | Dereferences a pointer to access the pointed value. | *ptr |
| ? : | Ternary conditional operator. | cond ? val_if_true : val_if_false |
Operator Precedence
Operators with higher precedence bind tighter. For instance, x = 5 + 3 * 2 evaluates to 11 because * outranks +.
| Category | Operators | Associativity |
|---|---|---|
| Postfix | () [] -> . ++ -- | Left to right |
| Unary | + - ! ~ ++ -- (type) * & sizeof | Right to left |
| Multiplicative | * / % | Left to right |
| Additive | + - | Left to right |
| Shift | << >> | Left to right |
| Relational | < <= > >= | Left to right |
| Equality | == != | Left to right |
| Bitwise AND | & | Left to right |
| Bitwise XOR | ^ | Left to right |
| Bitwise OR | | | Left to right |
| Logical AND | && | Left to right |
| Logical OR | || | Left to right |
| Conditional | ?: | Right to left |
| Assignment | = += -= *= /= %= >>= <<= &= ^= |= | Right to left |
| Comma | , | Left to right |
Functions
Function Parameters
Formal parameters receive data when a function is invoked. They behave like local variables. C supports two calling conventions:
| Method | Description |
|---|---|
| Call by Value | Copies the argument's value into the parameter. Altering the parameter inside the function does not affect the original argument. |
| Call by Reference | Passes the argument's address via pointers. Dereferencing the pointer allows the function to modify the original variable. |
Arrays
| Concept | Details |
|---|---|
| Multi-dimensional | Supports arrays of arrays, such as 2D matrices. |
| Passing to functions | Array names decay to pointers, allowing them to be passed as pointer arguments. |
| Returning from functions | Functions can return pointers to dynamically allocated arrays. |
| Pointer to an array | An array name acts as a pointer to its first element. |
| Static vs Dynamic | Static arrays have fixed sizes determined at compile time; dynamic arrays use memory allocation (e.g., malloc) to resize at runtime. |
Pointers
| Concept | Details |
|---|---|
| Arithmetic | Pointers can be incremented (++), decremented (--), or have integers added/subtracted. |
| Array of pointers | Arrays designed to hold memory addresses. |
| Pointer to pointer | A construct where a pointer stores the address of another pointer. |
| Passing to functions | Enables functions to alter external variables via reference passing. |
| Returning from functions | Functions can yield pointers to static or dynamically allocated memory. |
String Manipulation Functions
| Function | Purpose |
|---|---|
strcpy(dest, src) | Copies the source string into the destination buffer. |
strcat(dest, src) | Appends the source string to the end of the destination string. |
strlen(str) | Calculates the length of the string, excluding the null terminator. |
strcmp(str1, str2) | Returns 0 if identical, a negative value if str1 < str2, and a positive value if str1 > str2. |
strchr(str, ch) | Locates the first occurrence of a character in a string. |
strstr(str1, str2) | Finds the first occurrence of the substring str2 within str1. |