MEVIHUB

Get Hired Fast-Crush C Interviews with These Proven Mastery Questions

Table of Contents

What is a ternary operator in c

The ternary operator, also known as the conditional operator, is a unique operator in C and many other programming languages. It provides a concise way to express conditional statements in a single line of code.

The syntax of the ternary operator is:

condition ? expression1 : expression2

Here’s how it works:

#include <stdio.h>

int main() {
    int a = 10, b = 11;
    int result;

    // Using the ternary operator to determine if a is greater than b
    result = (a > b) ? 1 : 0;

    // Using the result in an if statement
    if (result == 1) {
        printf("a is greater than b\n");
    } else {
        printf("a is not greater than b\n");
    }

    return 0;
}

Converting 32-bit Hexadecimal Data to 8-bit Format in C

#include <stdio.h>
#include <stdint.h>

int main() {
    uint32_t hexValue = 0xAABBCCDD; // Example 32-bit hexadecimal data
    uint8_t byte[4]; // Array to store 8-bit format

    // Converting 32-bit hex value to 8-bit format
    byte[0] = (hexValue >> 24) & 0xFF; // Extracting most significant byte
    byte[1] = (hexValue >> 16) & 0xFF;
    byte[2] = (hexValue >> 8) & 0xFF;
    byte[3] = hexValue & 0xFF; // Extracting least significant byte

    // Displaying the result
    printf("Original 32-bit hex value: 0x%X\n", hexValue);
    printf("8-bit format:\n");
    for (int i = 0; i < 4; i++) {
        printf("Byte %d: 0x%X\n", i+1, byte[i]);
    }

    return 0;
}

Explanation:

Can you demonstrate how to manipulate individual bits in C, including setting, toggling, and deleting/Clear them?

#include <stdio.h>
#include <stdint.h>

// Function to set a specific bit in a given number
uint32_t setBit(uint32_t num, int pos) {
    return num | (1 << pos);
}

// Function to toggle a specific bit in a given number
uint32_t toggleBit(uint32_t num, int pos) {
    return num ^ (1 << pos);
}

// Function to delete (clear) a specific bit in a given number
uint32_t clearBit(uint32_t num, int pos) {
    return num & ~(1 << pos);
}

int main() {
    uint32_t num = 0b10101010; // Example number
    int pos = 2; // Position of the bit to manipulate

    // Setting the bit at position 'pos'
    printf("Setting bit at position %d: 0x%X\n", pos, setBit(num, pos));

    // Toggling the bit at position 'pos'
    printf("Toggling bit at position %d: 0x%X\n", pos, toggleBit(num, pos));

    // Deleting (clearing) the bit at position 'pos'
    printf("Clearing bit at position %d: 0x%X\n", pos, clearBit(num, pos));

    return 0;
}

Could you explain what structure padding is and how it benefits C programming

#include <stdio.h>

// Structure without padding
struct WithoutPadding {
    char a;
    int b;
    char c;
};

// Structure with padding
struct WithPadding {
    char a;
    char pad1[3]; // Padding added to align 'b' to a 4-byte boundary
    int b;
    char c;
};

int main() {
    // Size of structures
    printf("Size of struct WithoutPadding: %lu bytes\n", sizeof(struct WithoutPadding));
    printf("Size of struct WithPadding: %lu bytes\n", sizeof(struct WithPadding));

    return 0;
}

Candidate Explanation

Certainly! Structure padding is the process of adding additional bytes to a structure to ensure that each member of the structure is properly aligned in memory. The compiler often adds these padding bytes to optimize memory access and ensure efficient data retrieval. In C programming, structure members are typically aligned based on their data types. For example, integers often need to be aligned on 4-byte boundaries for optimal memory access. By padding the structure, the compiler can ensure that accessing structure members is more efficient, which can lead to better performance and memory utilization in the program.

Can you explain what the volatile keyword in C is used for and provide an example of its usage?

Candidate: “Certainly! The volatile keyword is used to inform the compiler that a variable’s value may change unexpectedly, without any action being taken by the code. This could happen due to external factors such as hardware registers, concurrent execution by multiple threads, or signal handlers. When a variable is declared as volatile, the compiler is instructed not to optimize accesses to that variable, ensuring that reads and writes are performed as expected.

Interviewer: Could you provide an example scenario where the volatile keyword is necessary?

Candidate: “Sure! Consider a scenario where we have a variable sensor_value representing data read from a sensor connected to a microcontroller. The value of sensor_value can change at any time due to external sensor readings, and the compiler should not optimize accesses to it. Therefore, we declare sensor_value as volatile to ensure that the compiler does not optimize away reads or reorder writes to the variable. This ensures that our program correctly reacts to changes in sensor data, as shown in the code example below.

#include <stdio.h>

volatile int sensor_value; // Declaring sensor_value as volatile

// Function to read sensor value
void read_sensor() {
    // Read sensor value from hardware
    sensor_value = read_sensor_data();
}

// Main program loop
int main() {
    while (1) {
        // Perform some processing based on sensor value
        process_sensor_data(sensor_value);
    }
    return 0;
}

In this example, sensor_value is declared as volatile because its value can change asynchronously due to external hardware events. The volatile keyword ensures that the compiler does not optimize accesses to sensor_value, allowing the program to correctly read and react to changes in sensor data.

Interviewer: Let’s dive deeper into the concept of volatile variables. You mentioned that declaring a variable as volatile informs the compiler that its value may change unexpectedly. Could you explain why this is important in certain scenarios?

Without volatile:

With volatile:

Could you explain the difference between the auto and register storage classes in C?

Candidate: “Certainly! Both auto and register are storage class specifiers in C, but they serve different purposes.

auto:

register

In summary, while both auto and register are storage class specifiers in C, auto is used to declare local variables with default storage behaviour, whereas register is used to suggest to the compiler that a variable should be stored in a CPU register for faster access.

Interviewer: Thank you for the explanation. Can you provide an example scenario where using register might be beneficial?

Candidate: “Certainly! In performance-critical sections of code, such as inner loops, where frequent access to a variable is required, using a register to suggest storing that variable in a CPU register can lead to performance improvements by reducing memory access times.

Example 1: Using auto Storage Class:

#include <stdio.h>

void autoExample() {
    auto int x = 10; // Declaring 'x' as auto, which is the default behavior for local variables
    printf("Value of auto variable 'x': %d\n", x);
}

int main() {
    autoExample();
    return 0;
}

Example 2: Using register Storage Class:

#include <stdio.h>

void registerExample() {
    register int y = 20; // Declaring 'y' as register to suggest storing it in a CPU register
    printf("Value of register variable 'y': %d\n", y);
}

int main() {
    registerExample();
    return 0;
}

Interviewer: Can you explain how to store data in a specific memory location, such as a register?

Candidate: “Certainly! To store data in a particular memory location, like a register, we typically use pointers to access that memory directly. However, it’s important to note that the use of register variables in C is merely a suggestion to the compiler, and the compiler may or may not honor this suggestion. Let me provide you with an example code snippet to demonstrate how this can be done.

#include <stdio.h>

int main() {
    // Declare a pointer to an integer and initialize it with the memory address of the register
    volatile int *register_ptr = (volatile int *)0x12345678; // Replace 0x12345678 with the actual memory address

    // Store data in the register memory location
    *register_ptr = 42;

    // Read data from the register memory location
    int data = *register_ptr;

    // Print the data read from the register
    printf("Data stored in the register: %d\n", data);

    return 0;
}

Could you please explain the memory sizes of integer, character, integer pointer, and character pointer in C?

Data TypeSize (in bytes)
char1
short2
int4
long4
long long8
float4
double8
long double12 or 16
char*4
int*4
void*4
structVaries
enumVaries
 

Write a program for Big-endian and little-endian

#include <stdio.h>

typedef union
{
    unsigned int n;
    char bytes[4];
}EndianCheck;

int main()
{
    EndianCheck endianCheck;
    endianCheck.n = 0x11223344;
    
    printf(" %x\n",endianCheck.bytes[1]);
    if(endianCheck.bytes[0] == 11)
    {
        printf(" BigIndian\n");
    }
    else {
        printf("Little Indian\n");
    }
    
}

Could you write a C program to reverse a character string using pointers, without using any library such as <string.h>?

#include <stdio.h>

// Function to calculate the length of a string
int Str_len(char *str) {
    int length = 0; // Initialize length counter

    // Loop through the string until null terminator is encountered
    while (*str != '\0') {
        length++; // Increment length for each character
        str++;    // Move to the next character in the string
    }
    return length; // Return the calculated length
}

int main() {
    char str[] = "devilal"; // Input string
    
    // Calculate the length of the input string using Str_len function
    int len = Str_len(str);
    
    // Create a character array to store the reversed string
    char Reverse[len];
    
    printf("Length of the string: %d\n", len); // Print the length of the string
    
    int count = len - 1; // Initialize the counter for the reversed string
    
    // Loop through the original string and store characters in reverse order
    for (int i = 0; i < len; i++) {
        Reverse[i] = str[count]; // Store the character at index 'count' in 'Reverse'
        count--; // Decrement 'count' to move to the previous character in the original string
    }
    
    printf("Reversed string: %s\n", Reverse); // Print the reversed string
    
    Reverse[len] = '\0'; // Add null terminator at the end of the reversed string
    
    return 0;
}

Explanation:

Can you write a C function to toggle a specific bit in a given number?

#include <stdio.h>
#include <stdint.h>

// Function to toggle a specific bit in a given number
uint32_t toggleBit(uint32_t num, int pos) {
    return num ^ (1 << pos);
}

int main() {
    uint32_t num = 0xAAAAAAAA; // Example value
    int pos = 3; // Bit position to toggle

    printf("Original number: 0x%X\n", num); // Print original number in hexadecimal format

    // Toggle the bit at the specified position
    uint32_t toggledNum = toggleBit(num, pos);

    printf("Toggled number: 0x%X\n", toggledNum); // Print toggled number in hexadecimal format

    return 0;
}

Explanation:

Could you explain the storage classes in C?

Candidate: Certainly! In C, storage classes determine the scope, lifetime, and visibility of variables and functions. There are four primary storage classes: auto, register, static, and extern.

  1. auto: Variables declared within a block (function or compound statement) without any storage class specifier default to auto. They are automatically allocated memory when the block is entered and deallocated when the block is exited. Their scope is limited to the block in which they are defined.
  2. register: The register storage class is used to suggest the compiler to store the variable in a register for faster access. However, the compiler may ignore this suggestion. Variables declared with the register storage class have the same lifetime and scope as auto variables.
  3. static: Variables and functions declared with the static storage class have a lifetime that extends throughout the program’s execution. When used with local variables, it retains the value between function calls. When used with global variables, it restricts the visibility of the variable to the file in which it is declared.
  4. extern: The extern storage class is used to declare variables and functions that are defined in other files. It extends the visibility of the variable or function to other files.
#include <stdio.h>

// Global variable with static storage class
static int globalStaticVar = 10;

// Global variable with extern storage class (declared in another file)
extern int globalExternVar;

int main() {
    // Local variable with auto storage class
    auto int localVarAuto = 20;

    // Local variable with register storage class
    register int localVarRegister = 30;

    // Static local variable
    static int localVarStatic = 40;

    printf("Local variable (auto): %d\n", localVarAuto);
    printf("Local variable (register): %d\n", localVarRegister);
    printf("Local variable (static): %d\n", localVarStatic);
    printf("Global variable (static): %d\n", globalStaticVar);
    printf("Global variable (extern): %d\n", globalExternVar);

    return 0;
}

Could you explain the compilation stages in C?

Explain about Structure padding and its advantage with example code

Structure padding refers to the addition of unused bytes between structure members to align them properly in memory. It’s primarily done to improve memory access efficiency, as most CPUs require data to be aligned to certain memory addresses for optimal performance.

Here’s an example demonstrating structure padding and its advantages:

#include <stdio.h>

// Define a structure without padding
struct WithoutPadding {
    char a;    // 1 byte
    int b;     // 4 bytes (on a typical 32-bit system)
    char c;    // 1 byte
};

// Define a structure with padding
struct WithPadding {
    char a;    // 1 byte
    char pad1[3]; // Padding to align 'b' to 4-byte boundary
    int b;     // 4 bytes
    char c;    // 1 byte
};

int main() {
    printf("Size of struct WithoutPadding: %zu bytes\n", sizeof(struct WithoutPadding));
    printf("Size of struct WithPadding: %zu bytes\n", sizeof(struct WithPadding));

    return 0;
}

In this example, the structure WithoutPadding has three members: a, b, and c. However, because int typically requires alignment on a 4-byte boundary, the compiler adds 3 bytes of padding after a to align b properly. As a result, the total size of WithoutPadding is 8 bytes (1 + 4 + 1 + 2 padding).

On the other hand, the structure WithPadding includes an explicit array pad1 to ensure proper alignment of b. This eliminates the need for the compiler to add padding, resulting in a more efficient memory layout. The total size of WithPadding is also 8 bytes (1 + 3 padding + 4 + 1), but the padding is managed explicitly.

Advantages of structure padding:

What are the compilation stages in c

In C, the compilation process involves several stages, each responsible for translating the source code into executable machine code. These stages typically include:

Preprocessing: In this stage, the preprocessor directives are processed. Directives such as #include, #define, and #ifdef are handled, and header files are included in the source code. Macros are also expanded during this stage.

Compilation: The preprocessed source code is translated into assembly language or an intermediate representation known as object code. This stage involves lexical analysis, syntax analysis, semantic analysis, and code generation.

Assembly: If the compiler generates assembly code in the previous stage, this code is assembled into machine language instructions by the assembler. Each assembly language instruction corresponds to one or more machine instructions.

Linking: The object code generated from multiple source files is combined and linked together to produce the final executable file. This stage resolves external references, such as function calls and global variables, by locating the corresponding definitions in other object files or libraries and creating a unified executable.

What is the difference between Actual Parameters and Formal Parameters? For example code

  1. Actual Parameters: Also known as arguments, actual parameters are the values or expressions passed to a function or procedure when it is called. These are the values that the function operates on. Actual parameters are provided by the caller of the function and can be variables, constants, or expressions.
  2. Formal Parameters: Also known as parameters or formal arguments, formal parameters are the variables listed in the function definition. They act as placeholders for the values to be supplied by the caller (actual parameters). The function uses these parameters to perform its task. Formal parameters are defined by the function or procedure and specify the type and number of values it expects to receive.

Here’s a simple example in C to illustrate the difference between actual parameters and formal parameters:

#include <stdio.h>

// Function declaration with formal parameters
void add(int a, int b);

int main() {
    int x = 5, y = 3;

    // Calling the function with actual parameters
    add(x, y);

    return 0;
}

// Function definition with formal parameters
void add(int a, int b) {
    int sum = a + b;
    printf("The sum of %d and %d is %d\n", a, b, sum);
}

In this example:

What is the difference between structure and union?

In C programming, both structures and unions are used to group different types of data under a single name. However, they have some key differences:

Memory Allocation:

Accessing Members:

Memory Utilization:

Here’s a simple example to illustrate the difference:

#include <stdio.h>

// Structure definition
struct Person {
    char name[20];
    int age;
};

// Union definition
union Data {
    int num;
    float fnum;
};

int main() {
    // Structure variable
    struct Person person1;
    person1.age = 30;

    // Union variable
    union Data data;
    data.num = 10;
    printf("Value of num: %d\n", data.num);
    data.fnum = 3.14;
    printf("Value of fnum: %.2f\n", data.fnum);

    return 0;
}

In this example:

What is Structure Padding for 32bit?

In a 32-bit system, the default alignment for most data types is usually their size. Here’s a brief overview of structure padding in a 32-bit system:

Here’s a simple example to illustrate structure padding in a 32-bit system:

#include <stdio.h>

// Define a structure with different data types
struct Example {
    char c;    // 1 byte
    int i;     // 4 bytes
    double d;  // 8 bytes
};

int main() {
    // Print the size of the structure
    printf("Size of struct Example: %lu bytes\n", sizeof(struct Example));

    return 0;
}

In this example:

What is DMA?

DMA stands for Direct Memory Access. It’s a feature found in many microcontrollers and computer systems that allows peripherals (such as UART, SPI, ADC, etc.) to transfer data to and from memory without involving the CPU. DMA controllers are hardware modules that manage these data transfers independently of the CPU.

In embedded C, DMA is commonly used to offload data transfer tasks from the CPU, freeing up CPU resources for other tasks. It’s especially useful for handling high-speed data transfers, such as streaming data from sensors, audio, or video sources.

DMA operations typically involve the following steps:

In embedded C programming, developers typically interact with DMA controllers through peripheral-specific registers and configuration settings provided by the microcontroller’s manufacturer. Configuration involves setting up DMA channels, specifying transfer parameters, and handling DMA-related interrupts or events.

Can you explain what a dangling pointer is in C

Candidate: Certainly. In C programming, a dangling pointer refers to a pointer that points to a memory location that has been deallocated or freed. When the memory associated with a pointer is deallocated using the free() function, but the pointer is not set to NULL, it becomes a dangling pointer. Attempting to dereference a dangling pointer can lead to undefined behavior, as it may point to a memory location that has been reused for other purposes or no longer exists.

Interviewer: Why is it important to handle dangling pointers properly?

Candidate: Handling dangling pointers properly is crucial because attempting to dereference a dangling pointer can lead to unexpected behavior and difficult-to-debug issues in the code. By setting dangling pointers to NULL after freeing the associated memory, we can prevent accidental dereferencing and make it easier to detect and diagnose memory-related errors during runtime.

Interviewer: Can you provide an example of how to handle a dangling pointer?

Here’s an example of a dangling pointer in C:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr = (int *)malloc(sizeof(int)); // Allocate memory
    *ptr = 42; // Assign a value to the allocated memory
    free(ptr); // Free the allocated memory
    // Now 'ptr' is a dangling pointer

    // Attempt to dereference the dangling pointer
    printf("Value at dangling pointer: %d\n", *ptr); // This can lead to undefined behavior

    return 0;
}

Here’s an example of how to handle a dangling pointer properly by setting it to NULL after freeing the associated memory

#include <stdio.h>
#include <stdlib.h>

int main() {
    // Allocate memory and assign it to a pointer
    int *ptr = (int *)malloc(sizeof(int));
    
    // Check if memory allocation was successful
    if (ptr != NULL) {
        // Use the allocated memory
        
        // Free the allocated memory
        free(ptr);
        
        // Set the pointer to NULL to avoid dangling pointer
        ptr = NULL;
    }
    
    return 0;
}

Can you explain the difference between a macro and an inline function in C, and provide example code for each

Candidate: Certainly. Both macros and inline functions are mechanisms used in C to improve code efficiency, but they operate differently and have different use cases.

A macro is a preprocessor directive that performs text substitution. When the code is compiled, the macro is replaced with its corresponding code wherever it is used in the program. Macros are typically defined using the #define directive and can take arguments. One common use case for macros is to define constants or perform simple operations that don’t require type checking.

On the other hand, an inline function is a function defined with the inline keyword, which suggests to the compiler that the function’s code should be inserted directly into the calling code instead of being called as a separate function. Inline functions are more flexible and type-safe compared to macros because they behave like regular functions and are subject to the same scoping and type rules. They also support type checking and can be used to define more complex operations.

Here’s an example of a macro and an inline function to demonstrate the difference:

What is the difference between int and unsigned int?

Aspectintunsigned int
Data RangeTypically -2,147,483,648 to 2,147,483,647 (depending on the platform)Typically 0 to 4,294,967,295 (depends on the platform)
Memory ConsumptionUsually 4 bytes (32 bits)Typically 0 to 4,294,967,295 (depending on the platform)
SignCan represent both positive and negative numbersRepresents only non-negative numbers (zero and positive integers)
OverflowCan overflow in both positive and negative directionsOverflow results in wraparound behavior, reaching 0 if positive overflow, or UINT_MAX if negative overflow

How does a switch statement differ from an if-else statement?

FeatureSwitch StatementIf-Else Statement
SyntaxUses switch keyword followed by a single expressionUses if, else if, and else keywords
UsageBest for multiple possible values of a single expressionMore versatile, handles a wider range of conditions
EvaluationExpression evaluated once jumps to matching caseConditions are evaluated sequentially until true
Fall-throughCan have fall-through behavior if no break is usedNo implicit fall-through behaviour
Expression TypesLimited to integral types or enumerated typesCan evaluate any boolean expression
Execution EfficiencyEfficient when multiple cases match the same expressionLess efficient for complex boolean expressions
ReadabilityClearer and more concise for simple value-based decisionsBetter for complex conditions with multiple checks

Exit mobile version