C Syntax Speed Run
You know how to program. Let’s map your existing knowledge to C in record time.Time: 4-6 hours
Goal: Write C code confidently
Assumed: You understand variables, functions, loops, arrays from another language
Goal: Write C code confidently
Assumed: You understand variables, functions, loops, arrays from another language
Types & Variables
Primitive Types
Copy
#include <stdio.h>
#include <stdint.h> // Fixed-width integers (C99)
#include <stdbool.h> // bool type (C99)
#include <limits.h> // Type limits
int main(void) {
// Character types
char c = 'A'; // At least 8 bits, may be signed or unsigned
signed char sc = -128; // Guaranteed signed
unsigned char uc = 255; // Guaranteed unsigned (0-255)
// Integer types (sizes are MINIMUM, vary by platform)
short s = 32767; // At least 16 bits
int i = 2147483647; // At least 16 bits (usually 32)
long l = 2147483647L; // At least 32 bits
long long ll = 9223372036854775807LL; // At least 64 bits (C99)
// Unsigned variants
unsigned int ui = 4294967295U;
unsigned long ul = 4294967295UL;
// Fixed-width integers (USE THESE for portable code)
int8_t i8 = -128;
int16_t i16 = -32768;
int32_t i32 = -2147483648;
int64_t i64 = -9223372036854775807LL;
uint8_t u8 = 255;
uint16_t u16 = 65535;
uint32_t u32 = 4294967295U;
uint64_t u64 = 18446744073709551615ULL;
// Floating point
float f = 3.14f; // Usually 32 bits
double d = 3.14159265359; // Usually 64 bits
long double ld = 3.14159265358979323846L; // At least 64 bits
// Boolean (C99)
bool flag = true; // Actually just int, 0 = false, non-zero = true
// Size type (for array indices, sizes)
size_t size = sizeof(int); // Unsigned, platform-specific
// Pointer difference type
ptrdiff_t diff = &i - &i; // Signed, for pointer arithmetic
return 0;
}
Critical:
int is NOT always 32 bits. On embedded systems it might be 16 bits. Use int32_t when you need exactly 32 bits.Type Sizes
Copy
#include <stdio.h>
#include <stdint.h>
int main(void) {
printf("char: %zu bytes\n", sizeof(char)); // Always 1
printf("short: %zu bytes\n", sizeof(short)); // >= 2
printf("int: %zu bytes\n", sizeof(int)); // >= 2
printf("long: %zu bytes\n", sizeof(long)); // >= 4
printf("long long: %zu bytes\n", sizeof(long long)); // >= 8
printf("float: %zu bytes\n", sizeof(float)); // Usually 4
printf("double: %zu bytes\n", sizeof(double)); // Usually 8
printf("void*: %zu bytes\n", sizeof(void*)); // 4 or 8 (32/64-bit)
printf("size_t: %zu bytes\n", sizeof(size_t)); // 4 or 8
return 0;
}
Integer Promotion & Conversions
Copy
#include <stdio.h>
#include <stdint.h>
int main(void) {
// Integer promotion: smaller types promoted to int in expressions
char a = 100, b = 100;
int result = a + b; // a and b promoted to int before addition
// This is why char arithmetic is safe (no overflow in expression)
char c = a + b; // Computed as int, then truncated to char (200)
// DANGER: Implicit signed/unsigned conversion
int negative = -1;
unsigned int positive = 1;
// -1 becomes UINT_MAX when converted to unsigned!
if (negative < positive) {
printf("Expected\n");
} else {
printf("Surprise! -1 > 1 in unsigned comparison\n"); // This prints!
}
// DANGER: Truncation
int32_t big = 100000;
int16_t small = big; // Truncated! small = -31072 (undefined if out of range)
return 0;
}
Control Flow (Quick Reference)
Copy
// If-else
if (condition) {
// ...
} else if (other_condition) {
// ...
} else {
// ...
}
// Switch (MUST use break, fall-through is default)
switch (value) {
case 1:
// ...
break;
case 2:
case 3:
// handles both 2 and 3 (intentional fall-through)
break;
default:
// ...
}
// Loops
for (int i = 0; i < n; i++) { }
while (condition) { }
do { } while (condition);
// Loop control
break; // Exit loop
continue; // Skip to next iteration
goto label; // Jump (use sparingly, but valid for error cleanup)
label:
// ...
The Comma Operator
Copy
// Less common, but you'll see it
for (int i = 0, j = n; i < j; i++, j--) {
// i increases, j decreases
}
// Evaluates left-to-right, returns rightmost value
int x = (a = 5, b = 6, a + b); // x = 11
Functions
Declaration vs Definition
Copy
// Declaration (prototype) - tells compiler function exists
int add(int a, int b);
// Definition - actual implementation
int add(int a, int b) {
return a + b;
}
// Declaration can omit parameter names
int multiply(int, int);
// Old-style declarations (avoid, but you'll see in legacy code)
int old_style(); // Accepts ANY number of arguments!
int modern_style(void); // Accepts NO arguments
Pass by Value (Always!)
Copy
void swap_broken(int a, int b) {
int temp = a;
a = b;
b = temp;
// Original values unchanged - we modified copies!
}
void swap_working(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
// Works because we passed addresses
}
int main(void) {
int x = 5, y = 10;
swap_broken(x, y); // x=5, y=10 still
swap_working(&x, &y); // x=10, y=5 now
return 0;
}
Static Functions
Copy
// Only visible within this file (internal linkage)
static int helper_function(int x) {
return x * 2;
}
// Can be called from other files (external linkage, default)
int public_function(int x) {
return helper_function(x) + 1;
}
Inline Functions (C99)
Copy
// Hint to compiler: inline the code
static inline int square(int x) {
return x * x;
}
Arrays
Basic Arrays
Copy
#include <stdio.h>
#include <string.h>
int main(void) {
// Stack-allocated arrays (size must be constant in C89)
int arr[5] = {1, 2, 3, 4, 5};
int zeros[100] = {0}; // All elements zero
int partial[5] = {1, 2}; // Rest are zero
int inferred[] = {1, 2, 3}; // Size inferred as 3
// Variable-length arrays (C99, optional in C11)
int n = 10;
int vla[n]; // Size determined at runtime (on stack!)
// Array size
size_t size = sizeof(arr) / sizeof(arr[0]); // 5
// Array decay: arrays decay to pointers when passed
void process(int *arr, size_t size); // Receives pointer, not array
return 0;
}
Array Decay: When you pass an array to a function, it decays to a pointer. You LOSE size information. Always pass size as a separate parameter.
Multi-dimensional Arrays
Copy
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// Memory layout: row-major (rows are contiguous)
// matrix[0][0], matrix[0][1], ..., matrix[0][3], matrix[1][0], ...
// Passing to functions requires all dimensions except first
void process_matrix(int mat[][4], size_t rows);
Strings
Strings in C are just null-terminated character arrays.Copy
#include <stdio.h>
#include <string.h>
int main(void) {
// String literal (stored in read-only memory)
const char *literal = "Hello"; // 6 bytes including '\0'
// Mutable string (array, not pointer)
char mutable[] = "Hello"; // Can modify
mutable[0] = 'J'; // Now "Jello"
// DANGER: This is undefined behavior
// char *bad = "Hello";
// bad[0] = 'J'; // UB! Modifying string literal
// String functions
size_t len = strlen("Hello"); // 5 (not counting '\0')
char dest[20];
strcpy(dest, "Hello"); // Copy (DANGER: no bounds check)
strncpy(dest, "Hello", sizeof(dest)); // Safer, but may not null-terminate!
dest[sizeof(dest) - 1] = '\0'; // Ensure null termination
strcat(dest, " World"); // Concatenate (DANGER)
strncat(dest, " World", sizeof(dest) - strlen(dest) - 1); // Safer
int cmp = strcmp("abc", "abd"); // < 0 (a < a, b < b, c < d)
char *found = strstr("Hello World", "World"); // Pointer to "World"
// Safe string formatting
char buffer[100];
int written = snprintf(buffer, sizeof(buffer), "Value: %d", 42);
// Returns number of chars that WOULD be written (for truncation detection)
return 0;
}
Structs
Basic Structs
Copy
#include <stdio.h>
#include <string.h>
// Definition
struct Point {
int x;
int y;
};
// Typedef for cleaner syntax
typedef struct {
char name[50];
int age;
float salary;
} Employee;
int main(void) {
// Initialization
struct Point p1 = {10, 20};
struct Point p2 = {.y = 20, .x = 10}; // Designated initializers (C99)
struct Point p3 = {0}; // All fields zero
Employee emp = {"John Doe", 30, 50000.0f};
// Access
p1.x = 15;
printf("%s is %d years old\n", emp.name, emp.age);
// Pointer access
struct Point *ptr = &p1;
ptr->x = 25; // Equivalent to (*ptr).x = 25
// Copy (entire struct, not pointer)
struct Point p4 = p1; // Deep copy of struct
return 0;
}
Struct Memory Layout & Padding
Copy
#include <stdio.h>
#include <stddef.h>
struct Padded {
char a; // 1 byte
// 3 bytes padding (to align int)
int b; // 4 bytes
char c; // 1 byte
// 3 bytes padding (to align struct to 4)
}; // Total: 12 bytes, not 6!
struct Packed {
int b; // 4 bytes
char a; // 1 byte
char c; // 1 byte
// 2 bytes padding
}; // Total: 8 bytes (better!)
// Check offsets
int main(void) {
printf("Padded size: %zu\n", sizeof(struct Padded));
printf("a offset: %zu\n", offsetof(struct Padded, a));
printf("b offset: %zu\n", offsetof(struct Padded, b));
printf("c offset: %zu\n", offsetof(struct Padded, c));
return 0;
}
Optimization: Order struct members from largest to smallest to minimize padding. This is especially important in hot paths and large arrays.
Bit Fields
Copy
struct Flags {
unsigned int active : 1; // 1 bit
unsigned int ready : 1; // 1 bit
unsigned int error_code : 4; // 4 bits (0-15)
unsigned int : 2; // 2 bits padding (unnamed)
unsigned int mode : 3; // 3 bits
}; // Typically 2-4 bytes depending on compiler
int main(void) {
struct Flags f = {0};
f.active = 1;
f.error_code = 15; // Max value for 4 bits
// f.error_code = 16; // UB! Overflow
return 0;
}
Unions
Unions share memory between members—only one is valid at a time.Copy
#include <stdio.h>
#include <stdint.h>
union Value {
int32_t i;
float f;
char bytes[4];
}; // Size = max member size (4 bytes)
// Type-punning for byte inspection
void print_float_bytes(float f) {
union {
float f;
uint8_t bytes[4];
} u = {.f = f};
printf("Float %f as bytes: ", f);
for (int i = 0; i < 4; i++) {
printf("%02x ", u.bytes[i]);
}
printf("\n");
}
// Tagged union (common pattern)
typedef enum { INT, FLOAT, STRING } ValueType;
typedef struct {
ValueType type;
union {
int i;
float f;
char *s;
} data;
} TaggedValue;
void print_value(TaggedValue *v) {
switch (v->type) {
case INT: printf("%d\n", v->data.i); break;
case FLOAT: printf("%f\n", v->data.f); break;
case STRING: printf("%s\n", v->data.s); break;
}
}
Enums
Copy
typedef enum {
RED, // 0
GREEN, // 1
BLUE // 2
} Color;
typedef enum {
SUCCESS = 0,
ERROR_FILE_NOT_FOUND = -1,
ERROR_PERMISSION_DENIED = -2,
ERROR_OUT_OF_MEMORY = -3
} ErrorCode;
// Underlying type is int (can use flags)
typedef enum {
FLAG_NONE = 0,
FLAG_READ = 1 << 0, // 1
FLAG_WRITE = 1 << 1, // 2
FLAG_EXEC = 1 << 2 // 4
} Permissions;
int main(void) {
Permissions p = FLAG_READ | FLAG_WRITE; // 3
if (p & FLAG_READ) {
printf("Has read permission\n");
}
return 0;
}
Compilation Model
Copy
# The four stages:
# 1. Preprocessing (-E): Handle #include, #define, #ifdef
# 2. Compilation (-S): C code → Assembly
# 3. Assembly (-c): Assembly → Object file (.o)
# 4. Linking: Object files → Executable
# Full pipeline
gcc -E main.c -o main.i # Preprocessor output
gcc -S main.c -o main.s # Assembly output
gcc -c main.c -o main.o # Object file
gcc main.o -o main # Link to executable
# Or all at once
gcc main.c -o main
# Important flags
gcc -Wall -Wextra -Werror -std=c11 -pedantic main.c -o main
# -Wall: Enable common warnings
# -Wextra: Enable extra warnings
# -Werror: Treat warnings as errors
# -std=c11: Use C11 standard
# -pedantic: Strict ISO C compliance
Quick Exercises
1
Type Sizes
Write a program that prints the size of all basic types on your system. Note which are different from what you expected.
2
Struct Padding
Create a struct with 3 members of different sizes. Use
sizeof and offsetof to visualize the padding.3
String Manipulation
Implement a safe
string_concat function that takes a destination buffer, its size, and two source strings, returning true if concatenation succeeded.4
Tagged Union
Implement a JSON-like value type using a tagged union that can hold null, bool, int, double, string, or error.
Next Up
Build Systems & Toolchain
Master GCC, Make, CMake, and the compilation process