C Programming: Core Syntax, Memory Architecture, and Systems-Level Implementation
1. Standard Library Headers and I/O Fundamentals
The C standard library exposes functionality through modular headers. <stdio.h> provides stream-oriented input/output operations, while <stdlib.h> contains utility functions for memory allocation, random number generation, and system command exceution.
Formatted Output with printf
The printf funciton writes formatted text to standard output. Format specifiers control how arguments are interpreted:
#include <stdio.h>
int main(void) {
int signed_val = -255;
unsigned int unsigned_val = 4000000000U;
double pi = 3.14159265359;
char letter = 'A';
char phrase[] = "Memory safety";
void *addr = &signed_val;
printf("Decimal signed: %d\n", signed_val);
printf("Decimal unsigned: %u\n", unsigned_val);
printf("Hexadecimal: %X\n", 0xABCD);
printf("Octal: %o\n", 077);
printf("Float precision: %.2f\n", pi);
printf("Scientific: %e\n", 6.022e23);
printf("Character: %c\n", letter);
printf("String: %s\n", phrase);
printf("Memory address: %p\n", addr);
return 0;
}
Width specifiers enable field alignment. A positive width right-justifies; a negative width left-justifies:
printf("[%10d]\n", 42); /* Right-aligned in 10 chars */
printf("[%-10d]\n", 42); /* Left-aligned in 10 chars */
printf("[%05d]\n", 42); /* Zero-padded to 5 digits */
Escape Sequences and Special Characters
Non-printable characters require escape notation:
\\- Literal backslash\'- Single quote within character literals\"- Double quote within string literals\n- Newline (line feed)\t- Horizontal tab (typically 8-column alignment)\xNN- Hexadecimal value (e.g.,\x41for 'A')\NNN- Octal value (e.g.,\101for 'A')
Trigraph sequences (??=, ??(, etc.) map to single characters for legacy character set compatibility, though modern compilers often require explicit flags to recognize them.
Formatted Input with scanf
scanf parses standard input using format specifiers. It requires memory addresses for storage:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void) {
int age;
float salary;
char name[50];
printf("Enter age, salary, and name: ");
scanf("%d %f %s", &age, &salary, name);
printf("Received: %d years, $%.2f, %s\n", age, salary, name);
return 0;
}
Note that array names decay to pointers, so name does not require the address-of operator (&), whereas scalar variables do.
Utility Functions in stdlib.h
Random Number Generation:
rand() generates pseudo-random integers in the range [0, RAND_MAX]. To avoid identical sequences across executions, seed the generator with srand():
#include <stdlib.h>
#include <time.h>
int main(void) {
srand((unsigned int)time(NULL));
for (int idx = 0; idx < 5; idx++) {
printf("Random %d: %d\n", idx, rand() % 100);
}
return 0;
}
System Commands:
#include <stdlib.h>
int main(void) {
system("title C_Demo_Console");
system("mode con cols=80 lines=25");
system("cls");
system("notepad.exe");
return 0;
}
2. Fundamental Data Types and Representation
C provides integer, floating-point, and character types with platform-dependent but minimum-guaranteed sizes.
Integer Types
| Type | Typical Size | Range (Visual C++) |
|---|---|---|
short |
2 bytes | -32,768 to 32,767 |
int |
4 bytes | -2,147,483,648 to 2,147,483,647 |
long |
4 bytes (Windows) | Same as int |
long long |
8 bytes | -9.22e18 to 9.22e18 |
The standard guarantees: sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long).
Floating-Point Types
float: 4 bytes, single precision (approximately 7 decimal digits)double: 8 bytes, double precision (approximately 15 decimal digits)
Character Types
char stores single byte characters (typically ASCII). The signedness of plain char is implementation-defined; use signed char or unsigned char for portability.
Integer literals accept prefixes: 0 for octal (e.g., 017), 0x for hexadecimal (e.g., 0xFF), and modern compilers support 0b for binary (e.g., 0b1010).
3. Operators and Expressions
Arithmetic Operators
Integer division truncates toward zero. To obtain floating-point results, at least one operand must be floating-point:
int a = 7, b = 2;
printf("%d\n", a / b); /* Outputs: 3 */
printf("%f\n", 7.0 / 2); /* Outputs: 3.500000 */
When summing harmonic series, force floating-point conversion:
float harmonic = 0.0f;
for (int term = 1; term <= 100; term++) {
harmonic += 1.0f / term; /* 1.0f ensures float division */
}
Bitwise Operations
Operators work on two's complement representations:
<<: Left shift (fills with zeros)>>: Right shift (arithmetic: sign-bit fill for negatives; logical: zero fill for unsigned)&: Bitwise AND (masking)|: Bitwise OR (setting bits)^: Bitwise XOR (toggling)~: Bitwise NOT (inversion)
Setting and clearing bits:
unsigned int flags = 0x00;
flags |= (1 << 3); /* Set bit 3 */
flags &= ~(1 << 3); /* Clear bit 3 */
flags ^= (1 << 3); /* Toggle bit 3 */
Counting set bits (population count):
int popcount(unsigned int value) {
int count = 0;
while (value) {
value &= (value - 1); /* Clear least significant set bit */
count++;
}
return count;
}
Shift Operations and Sign Extension
Left shift always fills with zeros. Right shift behavior depends on type:
signed int negative = -8; /* Binary: ...11111000 */
signed int shifted = negative >> 2; /* Arithmetic: ...11111110 (remains -2) */
unsigned int positive = 0xFFFFFFF8;
unsigned int logical_shift = positive >> 2; /* Logical: 0x3FFFFFFE */
Logical Operators
&& and || exhibit short-circuit evaluation:
int x = 0, y = 5;
if (x++ && y++) { /* y++ never executes because x++ evaluates to 0 (false) */
/* unreachable */
}
Ternary Operator
The conditional operator ?: returns values, not references, preventing direct assignment to its results:
int val_a = 10, val_b = 20;
int max_val = (val_a > val_b) ? val_a : val_b; /* Valid */
/* (val_a > val_b) ? val_a : val_b = 30; */ /* Invalid: not an lvalue */
sizeof Operator
sizeof yields the size in bytes of a type or expression. It does not evaluate its operand at runtime:
int buffer[100];
size_t array_size = sizeof(buffer); /* Total bytes: 400 */
size_t element_size = sizeof(buffer[0]); /* Single element: 4 */
size_t element_count = sizeof(buffer) / sizeof(buffer[0]); /* 100 */
int x = 10;
printf("%zu\n", sizeof(x = x + 100)); /* Prints 4; x remains 10 */
Precedence and Associativity
High precedence: postfix operators ([], (), ->, .), unary operators (!, ~, ++, --, *, &, sizeof), multiplicative (*, /, %).
Low precedence: comma operator (,).
Associativity is left-to-right for most binary operators, right-to-left for assignment and unary operators.
4. Variable Scope and Storage Duration
Scope Categories
Block Scope: Variables declared inside {} are accessible only within that block. They contain indeterminate values unless initialized.
File Scope: Variables declared outside functions are accessible throughout the translation unit. They initialize to zero by default.
#include <stdio.h>
int global_counter; /* File scope, initialized to 0 */
void increment(void) {
static int persistent = 0; /* Static storage duration, retains value */
int local_temp = 0; /* Automatic storage, recreated each call */
persistent++;
local_temp++;
printf("Static: %d, Local: %d\n", persistent, local_temp);
}
int main(void) {
for (int i = 0; i < 3; i++) {
increment();
}
return 0;
}
Output:
Static: 1, Local: 1
Static: 2, Local: 1
Static: 3, Local: 1
Storage Class Specifiers
auto: Implicit for local variables (rarely written explicitly).static: Extends lifetime to program duration; limits visibility for globals to file scope.register: Suggests storage in CPU registers (compiler hint, modern compilers optimize automatically).extern: Declares external linkage, referencing variables defined in other translation units.
5. Constants and Literals
Literal Constants
Immediate values: 42, 3.14, 'X', "String literal".
const Qualifier
Creates read-only variables that occupy memory (unlike preprocessor constants):
const int max_iterations = 1000;
/* max_iterations = 500; */ /* Compile error: read-only */
Enumerations
enum defines named integer constants with automatic increment:
enum StatusCode {
OK = 200,
CREATED = 201,
BAD_REQUEST = 400,
NOT_FOUND = 404, /* Subsequent values increment by 1 if unspecified */
ERROR = 500
};
enum StatusCode current = OK;
Enumerations provide type safety and debugging symbols compared to #define macros.
Preprocessor Constants
#define BUFFER_SIZE 1024
#define PI 3.14159
#define SQUARE(x) ((x) * (x)) /* Macro with parentheses for safety */
Macros perform textual substitution before compilation. Parentheses around parameters and the entire expression prevent operator precedence errors.
6. String Manipulation and Memory Layout
String Representation
C strings are null-terminated character arrays. The null byte '\0' (value 0) marks the end.
char str1[] = "Hello"; /* Array size 6: 'H','e','l','l','o','\0' */
char str2[] = {'H','i'}; /* No null terminator: unsafe for string functions */
char str3[5] = "Test"; /* Exactly fits: 'T','e','s','t','\0' */
String Length and Size
strlen() counts characters until (but not including) the null terminator. sizeof() returns the total memory allocation:
char greeting[] = "Hello";
size_t len = strlen(greeting); /* 5 */
size_t size = sizeof(greeting); /* 6 */
Standard String Functions
Copying:
char destination[50];
strcpy(destination, "Source text"); /* Unsafe: no bounds checking */
strncpy(destination, "Limited", 10); /* Safer: max 10 chars */
Concatenation:
strcat(destination, " appended"); /* Appends to end */
strncat(destination, " safe", 5); /* Appends max 5 chars plus null */
Comparison:
strcmp() returns negative, zero, or positive values based on lexicographical ordering:
if (strcmp(str1, str2) == 0) { /* Equal strings */
/* ... */
}
Searching:
strstr() locates substrings; strchr() finds characters.
Memory Manipulation Functions
memcpy() copies raw memory (undefined behavior if regions overlap). memmove() handles overlapping regions safely:
int data[] = {1, 2, 3, 4, 5};
memmove(&data[2], &data[0], 3 * sizeof(int)); /* data becomes {1,2,1,2,3} */
memset() initializes memory to a specific byte value:
int arr[100];
memset(arr, 0, sizeof(arr)); /* Zeroes entire array */
7. Control Flow Structures
Conditional Statements
if-else chains:
int score = 85;
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else {
grade = 'F';
}
Avoid chained comparisons like 0 <= x <= 10; use 0 <= x && x <= 10.
switch statements:
enum Color { RED, GREEN, BLUE };
enum Color pixel = GREEN;
switch (pixel) {
case RED:
printf("Red channel\n");
break;
case GREEN:
printf("Green channel\n");
break;
case BLUE:
printf("Blue channel\n");
break;
default:
printf("Unknown\n");
break;
}
Without break, execution falls through to subsequent cases.
Loop Constructs
while loops:
int num = 12345;
int sum_digits = 0;
while (num > 0) {
sum_digits += num % 10;
num /= 10;
}
for loops:
for (int i = 0, j = 10; i < j; i++, j--) {
printf("i=%d, j=%d\n", i, j);
}
Omitting the initialization or increment expressions creates a while-equivalent structure.
do-while loops:
Executes body at least once before checking condition:
int input;
do {
printf("Enter positive number: ");
scanf("%d", &input);
} while (input <= 0);
Jump Statements
break: Exits innermost loop or switch.continue: Skips to next iteration.return: Exits function, optionally returning value.goto: Transfers control to labeled statement (avoid in structured programming).
8. Functions and Program Flow
Function Declaration and Definition
/* Declaration (prototype) */
double calculate_circle_area(double radius);
/* Definition */
double calculate_circle_area(double radius) {
return 3.14159 * radius * radius;
}
Parameters are passed by value. Modifications to parameters do not affect arguments. To modify caller variables, pass pointers:
void swap_values(int *px, int *py) {
int temp = *px;
*px = *py;
*py = temp;
}
Recursion
Functions that call themselves require:
- Base case (termination condition)
- Progress toward base case
Factorial:
unsigned long long factorial(unsigned int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
Fibonacci (inefficient recursive):
unsigned long long fib(unsigned int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
Fibonacci (efficient iterative):
unsigned long long fib_iterative(unsigned int n) {
if (n < 2) return n;
unsigned long long prev = 0, curr = 1;
for (unsigned int i = 2; i <= n; i++) {
unsigned long long next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
Function Pointers and Callbacks
Function pointers enable polymorphic behavior and callbacks:
#include <stdio.h>
#include <stdlib.h>
int compare_ascending(const void *a, const void *b) {
int arg1 = *(const int*)a;
int arg2 = *(const int*)b;
return (arg1 > arg2) - (arg1 < arg2);
}
int main(void) {
int dataset[] = {64, 34, 25, 12, 22, 11, 90};
size_t n = sizeof(dataset) / sizeof(dataset[0]);
qsort(dataset, n, sizeof(int), compare_ascending);
for (size_t i = 0; i < n; i++) {
printf("%d ", dataset[i]);
}
return 0;
}
9. Arrays and Pointer Arithmetic
Array Fundamentals
Arrays store contiguous elements of the same type:
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
Initialization omits elements default to zero:
int sparse[10] = {1, 2}; /* Index 0=1, 1=2, rest=0 */
Array-Pointer Relationship
Array names decay to pointers to the first element in most expressions:
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; /* Equivalent to &arr[0] */
printf("%d\n", arr[2]); /* Array indexing */
printf("%d\n", *(arr + 2)); /* Pointer arithmetic */
printf("%d\n", p[2]); /* Pointer indexing */
printf("%d\n", 2[arr]); /* Unconventional but valid: *(2+arr) */
Pointer arithmetic scales by the size of the pointed-to type:
int *ip = arr;
ip++; /* Advances by sizeof(int) bytes */
Array Parameters
When passed to functions, arrays decay to pointers. The called function cannot determine the array size:
void process_array(int *buffer, size_t length) {
for (size_t idx = 0; idx < length; idx++) {
buffer[idx] *= 2;
}
}
/* Call: */
process_array(data, sizeof(data) / sizeof(data[0]));
For 2D arrays, column size must be specified:
void print_matrix(int rows, int cols, int mat[][4]) {
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
printf("%d ", mat[r][c]);
}
printf("\n");
}
}
10. Advanced Pointer Concepts
Pointer Types and Void Pointers
void* represents a generic pointer type that can point to any data type. It cannot be dereferenced without casting:
void *generic = malloc(sizeof(double));
double *dp = (double*)generic;
*dp = 3.14159;
Pointer to Pointers
Used for dynamic 2D arrays or modifying pointer values:
int value = 42;
int *ptr = &value;
int **pptr = &ptr;
printf("%d\n", **pptr); /* Dereferences twice to reach value */
Complex Declarations
Reading right-to-left (or using the "Clockwise/Spiral Rule"):
int *arr[10]; /* Array of 10 pointers to int */
int (*arr)[10]; /* Pointer to array of 10 ints */
int (*func)(int); /* Pointer to function taking int, returning int */
int (*arr_func[5])(int); /* Array of 5 function pointers */
Dynamic Memory Allocation
#include <stdlib.h>
/* Single object */
int *single = malloc(sizeof(int));
*single = 100;
free(single);
single = NULL;
/* Array */
size_t count = 100;
int *dynamic_arr = calloc(count, sizeof(int)); /* Zero-initialized */
if (dynamic_arr == NULL) { /* Handle error */ }
/* Resize */
count *= 2;
int *resized = realloc(dynamic_arr, count * sizeof(int));
if (resized != NULL) {
dynamic_arr = resized;
}
Common errors:
- Dereferencing NULL
- Using memory after
free(dangling pointer) - Double-free
- Memory leaks (losing pointer to allocated memory)
11. Memory Storage and Endianness
Memory Organization
Program memory segments:
- Stack: Local variables, function parameters, return addresses. Grows downward.
- Heap: Dynamically allocated memory. Grows upward.
- Data: Global and static variables (initialized and uninitialized/BSS).
- Text: Program code (read-only).
Endianness
Little-endian (x86, ARM): Least significant byte at lowest address. Big-endian (Network protocols, some RISC): Most significant byte at lowest address.
Detection:
int check_endianness(void) {
union {
int i;
char c[sizeof(int)];
} test;
test.i = 1;
return (test.c[0] == 1); /* True if little-endian */
}
Integer Representation
Signed integers use two's complement:
- Positive: Standard binary
- Negative: Invert bits, add 1
Range of signed n-bit integer: [-2^(n-1), 2^(n-1) - 1] Unsigned range: [0, 2^n - 1]
Floating-Point Representation (IEEE 754)
Single-precision (32-bit) format:
- 1 bit: Sign (S)
- 8 bits: Exponent (E, biased by 127)
- 23 bits: Mantissa/Significand (M, implicit leading 1)
Value = (-1)^S × 1.M × 2^(E-127)
Special values: E=0 (denormalized), E=255 (infinity/NaN).
12. Structures, Unions, and Bit-Fields
Structures
Aggregate types combining heterogeneous data:
struct PacketHeader {
uint16_t source_port;
uint16_t dest_port;
uint32_t sequence_num;
uint32_t ack_num;
uint8_t data_offset;
uint8_t flags;
uint16_t window_size;
};
struct PacketHeader tcp_hdr = {
.source_port = 8080,
.dest_port = 443,
.sequence_num = 1000
};
Memory Alignment
Compilers insert padding to satisfy alignment requirements:
struct Example {
char c; /* 1 byte + 3 padding */
int i; /* 4 bytes */
char d; /* 1 byte + 3 padding */
}; /* Total: 12 bytes, not 6 */
Pack structures to minimize padding (may reduce performance):
#pragma pack(push, 1)
struct PackedExample { /* layout without padding */ };
#pragma pack(pop)
Unions
All members share the same memory location:
union DataValue {
int i;
float f;
char str[20];
};
union DataValue val;
val.i = 123;
val.f = 98.6; /* Overwrites val.i */
Size equals the largest member.
Bit-Fields
Memory-efficient storage for flags and small values:
struct DeviceStatus {
unsigned int is_online : 1;
unsigned int error_code : 4;
unsigned int priority : 3;
unsigned int : 24; /* Padding to fill 32 bits */
};
13. Preprocessor and Compilation
Compilation Phases
- Preprocessing: Macro expansion,
#includeresolution, comment removal. - Compilation: Syntax analysis, optimization, assembly generation.
- Assembly: Object code generation.
- Linking: Symbol resolution, library binding.
Preprocessor Directives
Conditional compilation:
#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s\n", msg)
#else
#define LOG(msg) ((void)0)
#endif
Macro operators:
#define STRINGIFY(x) #x /* Converts x to string */
#define CONCAT(a, b) a##b /* Concatenates tokens */
Predefined macros:
__FILE__: Current filename__LINE__: Current line number__DATE__: Compilation date__TIME__: Compilation time__func__: Current function name (C99)
Header Guards
Prevent multiple inclusion:
#ifndef MODULE_H
#define MODULE_H
/* declarations */
#endif /* MODULE_H */
Or use #pragma once (non-standard but widely supported).
14. File I/O Operations
Opening and Closing
FILE *fp = fopen("data.txt", "r"); /* Modes: r, w, a, r+, w+, a+, rb, wb */
if (fp == NULL) {
perror("Failed to open file");
return EXIT_FAILURE;
}
/* operations */
fclose(fp);
fp = NULL;
Formatted I/O
fprintf(fp, "ID: %d, Score: %.2f\n", 42, 95.5);
fscanf(fp, "%d %f", &id, &score);
Binary I/O
struct Record rec;
fwrite(&rec, sizeof(struct Record), 1, fp);
struct Record buffer[100];
size_t count = fread(buffer, sizeof(struct Record), 100, fp);
Positioning
fseek(fp, 0L, SEEK_SET); /* Beginning */
fseek(fp, 0L, SEEK_END); /* End */
long pos = ftell(fp); /* Current position */
rewind(fp); /* Reset to beginning */
Standard Streams
stdin: Standard input (keyboard)stdout: Standard output (console)stderr: Standard error (unbuffered output)
15. Practical Algorithms and Problem Solving
Prime Number Detection
#include <stdbool.h>
#include <math.h>
bool is_prime(unsigned int n) {
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (unsigned int i = 3; i <= sqrt(n); i += 2) {
if (n % i == 0) return false;
}
return true;
}
Greatest Common Divisor (Euclidean Algorithm)
unsigned int gcd(unsigned int a, unsigned int b) {
while (b != 0) {
unsigned int remainder = a % b;
a = b;
b = remainder;
}
return a;
}
Tower of Hanoi
void move_disk(int disk, char source, char dest) {
printf("Move disk %d from %c to %c\n", disk, source, dest);
}
void hanoi(int n, char source, char auxiliary, char dest) {
if (n == 1) {
move_disk(n, source, dest);
return;
}
hanoi(n - 1, source, dest, auxiliary);
move_disk(n, source, dest);
hanoi(n - 1, auxiliary, source, dest);
}
String Rotation Check
#include <string.h>
bool is_rotation(const char *s1, const char *s2) {
if (strlen(s1) != strlen(s2)) return false;
size_t len = strlen(s1);
char *concat = malloc(2 * len + 1);
if (!concat) return false;
strcpy(concat, s1);
strcat(concat, s1);
bool result = (strstr(concat, s2) != NULL);
free(concat);
return result;
}
Finding Single Numbers (XOR Method)
void find_two_singles(int arr[], size_t n, int *first, int *second) {
int combined_xor = 0;
for (size_t i = 0; i < n; i++) {
combined_xor ^= arr[i];
}
/* Find rightmost set bit */
int diff_bit = combined_xor & (-combined_xor);
*first = 0;
*second = 0;
for (size_t i = 0; i < n; i++) {
if (arr[i] & diff_bit) {
*first ^= arr[i];
} else {
*second ^= arr[i];
}
}
}
Diamond Pattern Generation
void print_diamond(int rows) {
/* Upper half */
for (int i = 1; i <= rows; i++) {
for (int space = 0; space < rows - i; space++) printf(" ");
for (int star = 0; star < 2 * i - 1; star++) printf("*");
printf("\n");
}
/* Lower half */
for (int i = rows - 1; i >= 1; i--) {
for (int space = 0; space < rows - i; space++) printf(" ");
for (int star = 0; star < 2 * i - 1; star++) printf("*");
printf("\n");
}
}
Young's Matrix Search
bool search_young_matrix(int **matrix, int rows, int cols, int target) {
int row = 0;
int col = cols - 1;
while (row < rows && col >= 0) {
if (matrix[row][col] == target) return true;
if (matrix[row][col] > target) {
col--;
} else {
row++;
}
}
return false;
}
Narcissistic Numbers (Armstrong Numbers)
#include <math.h>
void find_armstrong_numbers(int limit) {
for (int num = 1; num <= limit; num++) {
int original = num;
int sum = 0;
int digits = (int)log10(num) + 1;
int temp = num;
while (temp > 0) {
int digit = temp % 10;
sum += (int)pow(digit, digits);
temp /= 10;
}
if (sum == original) {
printf("%d is an Armstrong number\n", original);
}
}
}