C Programming Interview Prep Guide
C Programming Interview Prep Guide
Dynamic memory allocation for a 2D array in C can be executed using both single and double pointers. Single pointer allocation involves simulating a 2D array with a contiguous block of memory and using pointer arithmetic to access elements. In double pointer method, memory is allocated for rows first (as pointers to int pointers), followed by individual row allocations using malloc. The structure for double method is: int **array = malloc(rows * sizeof(int*)); for each row, array[i] = malloc(columns * sizeof(int)); This allows dynamic management of both rows and columns .
Endianess refers to byte order at which data bytes are stored in memory. Little endian stores least significant byte at smallest address and big endian does the opposite. It affects reading of multibyte data types like integers. To check endianess: union { int num; char byte; } test = {1}; if (test.byte == 1) printf('Little Endian'); else printf('Big Endian'); This union checks which byte is stored first to determine endianess .
Storage classes define the scope, visibility, and lifespan of variables/functions in C. Static local variables have local scope within their function, retaining value across multiple function calls. They persist for the entire program execution even after the block exits. Global variables are accessible throughout the program and have a lifetime inception at program start until its termination. Thus, static ensures data persistence at function level, while global provides access at program level .
In C, strings are typically passed by reference either as an array or a pointer to a char. Passing a character pointer allows flexible memory use, but changes to strings may lead to undefined behavior if it's a string literal. Character arrays offer stability and mutability within bounds. Example: void func(char *ptr); vs void func(char arr[]);. Arrays allow direct manipulation, but pointers may more efficiently manage dynamic string operations .
When passing arrays to functions, a pointer to the array's first element is transferred, using void function_name(int arr[], int size). The entire sequence is accessible through pointer arithmetic. Individual element passing involves copying the element's value (e.g., int element = arr[i]; function_name(element);). While arrays reference a memory address directly, individual elements are passed by value, leading to separate function-local copies .
The ternary operator is a concise way to handle conditional expressions. For three numbers a, b, and c, one can use nested ternary operators to find the largest number: int max = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c); This expression first evaluates if a is greater than b, if true, it checks between a and c; otherwise, it checks between b and c, eventually assigning the greatest value to max .
Macros in C are preprocessor directives using #define to create symbolic constants or inline reusable code snippets. They run faster as they are processed before compilation, reducing runtime overhead. However, macros lack type safety and can lead to complex debugging, differing from functions that validate types and provide safer encapsulation. Macros also do not have the reliability of consistent side effects like functions do .
Qualifiers like 'const' and 'volatile' alter variable behavior. 'const' prevents any modification after initialization, ensuring compile-time protection and optimizing read-only data. 'volatile' informs the compiler to refrain from optimizing the variable, emphasizing its potential for changes outside program control, such as hardware I/O, relevant in embedded systems .
Function pointers in C enable dynamic function call capabilities, enhancing flexibility for plugins, callbacks, or advanced message handling. They are crucial for implementing callback functions for event-driven architectures or passing functions as arguments. Syntax: void (*func_ptr) (int) = &function_name; allows dynamic runtime decision making, such as choosing algorithm variants within the same function pointer type .
The XOR operator is versatile in practical applications such as toggling bits and performing specific bit manipulations efficiently without temporary variables. It can be used to swap two variables without a temporary storage due to its properties: applying XOR twice reverts the bit sequence to the original. Here is a swap operation example: For two integers a and b, to swap values: a = a ^ b; b = a ^ b; a = a ^ b; After these operations, a and b are swapped without using extra space .