Core C Programming: Syntax, Types, Control Flow, and Arrays
Origins and Characteristics of C
C emerged from Bell Labs, crafted by Dennis Ritchie to facilitate the development of the Unix operating system. Prior languages were either too hardware-bound (Assembly) or lacked the necessary abstraction. C bridged this gap, evolving from the B language to offer both high-level structures and low-level memory manipulation.
Key attributes include:
- Efficiency: Compiles directly to optimized machine instructions.
- Portabiltiy: Codebase adapts across different hardware architectures with minimal changes.
- Modularity: Supports functional decomposition and structured programming.
- Standardization: Governed by ANSI/ISO standards ensuring long-term stability.
Basic Syntax and Structure
A C program relies on a defined entry point and preprocessor directives.
#include <stdio.h>
int main(void) {
printf("System initialized.\n");
return 0;
}
#include <stdio.h>: Imports the standard I/O library header.main(): The mandatory entry point of execution.printf(): Outputs formatted text to the console.return 0;: Signals successful termination to the OS.- Semicolons terminate statements. Curly braces define code blocks.
Comments are written as // for single lines or /* ... */ for blocks.
Variables and Keywords
Variables must be declared with a data type before use. Naming rules dictate that identifiers begin with a letter or underscore, avoiding reserved keywords.
int counter; // Declaration
counter = 42; // Initialization
float ratio = 3.14f; // Declaration and initialization
Reserved keywords (e.g., int, return, while, struct, const) cannot be used as variable names.
Data Types and Memory Sizes
Data types define the size and interpretation of variables.
-
Character:
char(1 byte). Stores ASCII values. c char letter = 'A'; printf("%c %d\n", letter, letter); // Prints A 65 // Case conversion char lower = letter + ('a' - 'A'); -
Boolean:
_Boolorboolvia<stdbool.h>(1 byte,trueorfalse). -
Integers:
short,int,long,long long. -
Floating-point:
float,double,long double.
The sizeof operator returns memory consumption in bytes (result is of type size_t, printed using %zu).
printf("int size: %zu bytes\n", sizeof(int)); // Typically 4
printf("double size: %zu bytes\n", sizeof(double)); // Typically 8
Portable Integer Types
To ensure consistent byte widths across platforms, <stdint.h> provides fixed-width types like int8_t, uint32_t, and int64_t.
Constants
Values that remain unchanged during execution.
-
Literals: Direct values (e.g.,
100,3.14f,'X'). -
Preprocessor Macros: Defined globally without semicolons. c #define MAX_USERS 50
-
Const Qualifier: Enforces immutability at compile time. c const float PI = 3.14159f;
Numeral Systems and Memory Representation
C supports decimal, octal (prefix 0), hexadecimal (prefix 0x), and binary (prefix 0b) literals.
int dec = 15;
int oct = 017;
int hex = 0xF;
int bin = 0b1111;
printf("%d %#o %#x\n", dec, oct, hex);
Signed integers use two's complement representation. The most significant bit (MSB) indicates the sign (0 for positive, 1 for negative), with negative values formed by inverting bits and adding one.
Standard Input and Output
printf handles formatted output using specifiers (%d for int, %f for float, %c for char, %s for string, %p for pointesr).
scanf captures user input. It requires the address-of operator & for standard variables.
#define _CRT_SECURE_NO_WARNINGS // For MSVC compiler safety warnings
#include <stdio.h>
int main(void) {
int age;
scanf("%d", &age);
printf("Age received: %d\n", age);
return 0;
}
Operators
- Arithmetic:
+,-,*,/,%,++,-- - Relational:
==,!=,<,>,<=,>= - Logical:
&&(AND),||(OR),!(NOT) - Bitwise:
&,|,^,~,<<,>>
Bitwise Manipulation
Bitwise operators are essential for low-level hardware control and flags management.
#include <stdio.h>
#include <stdint.h>
int main(void) {
uint8_t config = 0b01010101;
// Set bit 4 (force to 1)
config |= (1 << 4);
// Clear bit 0 (force to 0)
config &= ~(1 << 0);
// Toggle bit 7
config ^= (1 << 7);
printf("Modified config: 0x%02X\n", config);
return 0;
}
Type Conversions
- Implicit (Widening): Automatically converts smaller types to larger types (e.g.,
inttodouble) to prevent data loss during arithmetic operations. - Explicit (Casting): Manually forcing a type conversion using the cast operator. c double pi = 3.14159; int truncated = (int)pi; // truncated becomes 3
Control Flow
Conditional Branching
if-else executes blocks based on boolean expressions.
if (score >= 90) {
printf("Grade A\n");
} else if (score >= 80) {
printf("Grade B\n");
} else {
printf("Grade C\n");
}
switch evaluates an integer or character against discrete cases.
switch (opcode) {
case '+': compute_add(); break;
case '-': compute_sub(); break;
default: printf("Invalid operation\n");
}
Iteration
-
for loop: Ideal for known iteration counts. c for (int i = 0; i < 10; i++) { /* ... */ }
-
while loop: Evaluates condition before execution. May execute zero times. c while (is_active) { /* ... */ }
-
do-while loop: Evaluates condition after execution. Guarantees at least one iteration. c do { /* ... */ } while (retry_flag);
break exits the loop entirely, while continue skips the current iteration and proceeds to the next cycle.
Functions
Functions encapsulate reusable logic. They must be declared (prototyped) or defined before use.
// Definition
int add(int x, int y) {
return x + y;
}
int main(void) {
int sum = add(5, 7); // Invocation
return 0;
}
Variables declared inside functions are local (stack-allocated, scope-limited). Variables declared outside are global (static storage duration, accessible file-wide).
Arrays
Arrays store contiguous blocks of homogenous data.
int data[5] = {10, 20, 30, 40, 50};
int length = sizeof(data) / sizeof(data[0]);
Array indices begin at 0. The array name acts as a constant pointer to the first element. Bounds checking is not enforced; out-of-bounds access causes undefined behavior.
Memory Layout and Dynamic Allocation
Elements reside in adjacent memory addresses. For a 32-bit int array, &data[1] is exactly 4 bytes after &data[0].
For variable-length requirements, dynamic memory allocation is used.
#include <stdlib.h>
int count = 20;
int *dyn_array = (int*)malloc(count * sizeof(int));
if (dyn_array != NULL) {
// Usage
dyn_array[0] = 99;
free(dyn_array); // Release memory
}
Array Operations and Function Passing
When passed to functions, arrays decay to pointers, losing size information. The length must be passed separately.
void reverse_array(int arr[], int len) {
int start = 0;
int end = len - 1;
while (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
}