- Purpose: A
struct
is used to group different data types into a single composite type. This helps in organizing and managing related data. - Example:
struct Point { int x, y; };
- Why Use: Groups related data together (e.g., coordinates for a 2D point), making the code easier to manage and more readable.
- Purpose: Struct embedding is used to embed one struct inside another, allowing the parent struct to access the fields and methods of the embedded struct. This technique is useful for inheritance-like behavior in C.
- Example:
struct Shape { void (*draw)(void *self); }; struct Circle { struct Shape base; int radius; };
- Why Use: Struct embedding enables code reuse and the creation of more complex data structures by composing existing ones. It mimics inheritance in object-oriented programming languages, facilitating polymorphism and modular design in C.
- Purpose: Bit fields allow packing multiple boolean flags or small integers into a single byte or word, saving memory space and optimizing performance.
- Example:
struct Flags { unsigned int is_valid : 1; unsigned int is_enabled : 1; };
- Why Use: Efficiently stores binary flags or small integer values, minimizing memory usage when space is limited, especially in embedded systems or hardware control.
- Purpose: This technique simulates polymorphism in C by using function pointers in structs, allowing different behavior based on the type of the struct.
- Example:
typedef struct { void (*speak)(void *self); } Animal; typedef struct { Animal base; char *name; } Dog; void dog_speak(void *self) { Dog *dog = (Dog *)self; printf("Woof! I am %s\n", dog->name); }
- Why Use: Enables behavior specialization (like polymorphism in OOP) in C, where a single interface (e.g.,
speak
) can have different implementations based on the type of the struct (Dog
,Cat
, etc.).
- Purpose: An empty struct is often used as a padding trick to align memory properly, especially for hardware or performance optimizations.
- Example:
typedef struct { char c; } EmptyStruct; typedef struct { EmptyStruct empty; // Padding for alignment int x; } AlignedStruct;
- Why Use: Aligns data to certain memory boundaries for performance or hardware reasons.
- Purpose: Allows dynamic array allocation inside a struct.
- Example:
typedef struct { int size; int array[]; // Flexible array member } ArrayStruct; ArrayStruct* create_array(int size) { ArrayStruct* s = malloc(sizeof(ArrayStruct) + sizeof(int) * size); s->size = size; return s; }
- Why Use: This trick is useful for creating structs with dynamic-sized arrays without needing to malloc for the array separately.
- Purpose: You can use a pointer to a pointer to access multi-dimensional arrays, saving memory.
- Example:
int rows = 3, cols = 3; int **matrix = malloc(rows * sizeof(int *)); for (int i = 0; i < rows; i++) { matrix[i] = malloc(cols * sizeof(int)); }
- Why Use: Saves memory when dealing with dynamically allocated multi-dimensional arrays, compared to allocating one large block for the entire matrix.
- Purpose: To calculate the number of elements in an array at compile time.
- Example:
int arr[10]; printf("Number of elements: %zu\n", sizeof(arr) / sizeof(arr[0]));
- Why Use: Prevents hardcoding the size of an array and is safer than manually counting the elements.
- Purpose: You can use pointer arithmetic to access array elements, which can be more efficient in certain cases.
- Example:
int arr[5] = {1, 2, 3, 4, 5}; int *p = arr; printf("%d\n", *(p + 2)); // Equivalent to arr[2]
- Why Use: Sometimes more efficient for specific low-level applications, though often less readable.
- Purpose: Pointer arithmetic allows direct manipulation of memory addresses to traverse arrays or data structures, often used for optimized access or iteration.
- Example:
int arr[5] = {1, 2, 3, 4, 5}; int *ptr = arr; printf("%d\n", *(ptr + 2)); // Access 3rd element
- Why Use: Provides a way to efficiently access and manipulate elements of arrays or buffers using pointers, offering low-level control over memory operations.
- Purpose: The
restrict
keyword tells the compiler that a pointer is the only one that will access the object it points to, enabling more aggressive optimizations. - Example:
void process(int *restrict arr1, int *restrict arr2);
- Why Use: Allows the compiler to optimize memory access, as it can assume that the two pointers (
arr1
andarr2
) do not overlap and do not point to the same memory.
- Purpose: Inline assembly allows you to insert assembly instructions directly into your C code, enabling fine control over hardware or performance-critical operations.
- Example:
asm("mov %0, %%eax" : : "r"(val));
- Why Use: Enables direct access to low-level operations and hardware instructions, which can be useful for optimization, device control, or platform-specific features.
- Purpose: The
__attribute__((aligned(n)))
directive specifies the memory alignment of a struct or variable, ensuring it is aligned ton
bytes, which can improve performance or meet hardware requirements. - Example:
struct __attribute__((aligned(16))) AlignedStruct { int x; };
- Why Use: Ensures that data is aligned to boundaries that are optimal for the target architecture, often improving access speed or satisfying alignment constraints for hardware interfaces.
- Purpose: You can use
memcpy
to copy non-trivial data types, but use with caution. - Example:
struct Point { int x, y; }; Point p1 = {1, 2}; Point p2; memcpy(&p2, &p1, sizeof(Point));
- Why Use: Avoids writing custom copy functions for structs, but use with care for POD (Plain Old Data) types only, not complex types.
- Purpose: Bitwise AND operation is used to quickly check if a number is even or odd. The least significant bit (LSB) determines the parity of the number.
- Example:
if (num & 1) printf("Odd\n"); else printf("Even\n");
- Why Use: The bitwise operation
num & 1
checks if the LSB is set to 1 (odd) or 0 (even), providing a faster alternative to modulus division for checking parity.
- Purpose: Bitwise XOR operation can be used to swap two variables without using a temporary variable.
- Example:
a = a ^ b; b = a ^ b; a = a ^ b;
- Why Use: This method leverages XOR to swap values directly in place, without needing extra memory for a temporary variable, making it a space-efficient approach.
- Purpose: Perform bitwise operations to manipulate specific bits of a number.
- Example:
num |= (1 << 3); // Set 3rd bit num &= ~(1 << 2); // Clear 2nd bit num ^= (1 << 1); // Toggle 1st bit
- Why Use: These operations allow you to efficiently manipulate individual bits in a variable, such as enabling/disabling features, flags, or control bits in a low-level application.
- Purpose: You can reverse the bits of an integer using bitwise operations.
- Example:
unsigned int reverse_bits(unsigned int n) { unsigned int reversed = 0; while (n) { reversed = (reversed << 1) | (n & 1); n >>= 1; } return reversed; }
- Why Use: Handy for low-level bit manipulation tasks, especially in embedded or systems programming.
- Purpose: Efficient and compact way to calculate powers of 2 using bit shifting.
- Example:
int power_of_two = 1 << n; // Equivalent to 2^n
- Why Use: Faster and cleaner than using
pow(2, n)
frommath.h
and doesn't require a loop.
- Purpose: Used in loops to prevent the compiler from optimizing out code that it thinks is redundant.
- Example:
volatile int i; for (i = 0; i < 1000; i++) { // Some code that shouldn't be optimized }
- Why Use: Prevents the compiler from optimizing code that's necessary for hardware interaction or other side-effect-heavy operations.
- Purpose: In deeply nested loops,
goto
can be used to jump out of multiple loops at once. - Example:
for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { if (some_condition()) { goto end; } } } end: printf("Exited loops.\n");
- Why Use: When you need to exit from multiple loops,
goto
can be clearer than using flags or more complex control flow.
- Purpose: Use
strcat
to efficiently concatenate strings in C. - Example:
char result[50] = "Hello, "; strcat(result, "world!");
- Why Use: Avoids manual array indexing and can be more efficient than manual copying for string concatenation.
- Purpose: Provide efficient alternatives to macros.
- Example:
inline int square(int x) { return x * x; }
- Purpose: Some compilers allow non-standard features for performance improvements or custom behaviors.
- Example:
#ifdef __GNUC__ __attribute__((always_inline)) inline void my_function() { // Always inline } #endif
- Why Use: These can offer optimizations, but be aware of portability issues across different compilers.
- Purpose: Use macros for quick, reusable code, but be cautious of side effects.
- Example:
#define SQUARE(x) ((x) * (x)) printf("%d\n", SQUARE(5)); // 25
- Why Use: Can be useful for quick calculations, but be careful about side effects from multiple evaluations of
x
.
- Purpose: When a function or variable is declared but not used, you can suppress unused warnings.
- Example:
void some_function(void) __attribute__((unused));
- Why Use: Handy for functions or variables that are only used in certain situations (e.g., debugging or profiling).
- Purpose: Use macros to create reusable debug logging functionality.
- Example:
#define LOG_DEBUG(msg) printf("[DEBUG] %s\n", msg);
- Why Use: Macros allow you to quickly add debugging output to your code without the need for extra function calls, making it easier to track the flow and state of your program during development.
- Purpose: Used to enforce that certain conditions hold true during runtime. If the condition fails, the program will terminate with an error message.
- Example:
#include <assert.h> assert(x > 0 && "x must be positive");
- Why Use: Assertions help in catching programming errors early by ensuring that critical conditions are met, particularly during development and debugging.