Top Embedded C Interview Questions for 2023 - IQCode

Embedded C Interview Questions for Freshers

Embedded C programming is a programming language used for developing embedded systems. It is popular due to its simplicity, efficiency, and portability. Embedded C differs from regular C because it is designed to work with hardware resources that have limitations on CPU and memory.

Now, let's take a look at some commonly asked interview questions for freshers in Embedded C.

1. What is Embedded C programming, and how is it different from the C language?

Answer: Embedded C is a programming language used for developing embedded systems, which have limitations on hardware resources such as memory size and CPU. Embedded C differs from the regular C language in its ability to interact with the hardware resources with necessary abstractions. Additionally, Embedded C is portable from one system to another.

What is Startup Code?

In software engineering, startup code refers to the initialization code that executes when a program starts up. This code initializes the program's data structures and defines the initial state of the program. Startup code is commonly used in embedded systems and operating systems, where it is responsible for initializing the system and preparing it for use. The startup code is usually written in assembly language or a low-level programming language to ensure that it executes quickly and efficiently. It is an essential part of a program's execution and is executed before the main function of the program is called.

Understanding Void Pointers in Embedded C

In Embedded C, a void pointer is a pointer that points to an unspecified type of data. It is often used when the data type of a variable is unknown or when a function can accept different data types as arguments.

For example, if we have a function that reads data from an external device, we may not know the data type of the incoming data. In this case, we can use a void pointer to create a generic pointer that can point to any datatype. Once the data type is determined, we can use typecasting to convert the void pointer to the correct pointer type.

Here's an example:



void processData(void *data) {
    if (data == NULL) {
        // Handle null pointer
        return;
    }
    int *intData = (int*) data; // Typecast void pointer to int pointer
    // Process intData
}

int main() {
    int myInt = 42;
    processData(&myInt); // Pass int pointer as void pointer
    return 0;
}

In the above example, we define a function "processData" that takes in a void pointer as an argument. Inside the function, we check if the void pointer is null and then typecast it to an integer pointer using "(int*) data". We can then process the integer data as needed.

In the main function, we create an integer variable "myInt" and pass its pointer as a void pointer to the "processData" function. The void pointer is then typecast back to an integer pointer inside the function.

Overall, void pointers are a powerful tool in Embedded C programming that allow for greater flexibility and dynamic type checking.

Reasons for using the volatile keyword

In Java, the volatile keyword is used to control the visibility and consistency of variables across multiple threads. Here are some reasons for using the volatile keyword:


// Declare the variable using the volatile keyword<br>
volatile int count = 0;<br><br>

// Access the variable in a thread-safe manner<br>
public void incrementCount() {<br>
    synchronized(this) {<br>
        count++;<br>
    }<br>
}

By declaring a variable as volatile, any changes made to that variable will be immediately visible to all other threads that use the variable. This ensures that all threads have access to the latest value of the variable, even if they are executing on different cores or processors.

Additionally, the use of the volatile keyword can help prevent data races and other synchronization issues that can occur when multiple threads access the same variable simultaneously. By properly synchronizing access to the variable, you can ensure that all threads operate on the variable in a thread-safe manner.

Differences between the const and volatile qualifiers in embedded C

In embedded C, the `const` and `volatile` qualifiers have different applications.

The `const` qualifier is used to define a variable whose value cannot be modified after initialization. It is mainly used to declare constants and also has some benefits such as reducing code size and increasing program execution speed.

On the other hand, the `volatile` qualifier is used to indicate that a variable's value can be changed outside the control of the program. This keyword is mainly used in scenarios where a memory-mapped peripheral or hardware register is being accessed. It ensures that the compiler does not optimize the code that accesses the register because its value can be changed by external factors.

To summarize, the `const` qualifier is used to specify that a variable is read-only, while the `volatile` qualifier is used to specify that a variable's value can be changed by factors outside of the program's control.

What is the concatenation operator in Embedded C?

In Embedded C, the concatenation operator is a double hash symbol (##) which is used to concatenate two tokens into a single token before actual processing by the preprocessor. This operator is commonly used in macros to create new identifiers or to concatenate with existing ones. Here is an example of how the concatenation operator works:


#define PIN_NUMBER 3
#define PIN_NAME(pin) P##pin

int pin = PIN_NUMBER;
int pinName = PIN_NAME(pin);

// The above code would translate to:
// int pin = 3;
// int pinName = P3;

In the above example, the macro `PIN_NAME` concatenates the letter "P" with the value of `pin` using the ## operator to create a new identifier `P3`. This concatenated identifier is then used to initialize the `pinName` variable.

Understanding Interrupt Latency

Interrupt Latency refers to the time lag between the occurrence of an interrupt and the initiation of the corresponding interrupt service routine (ISR) by the processor. In simpler terms, it is the amount of time a system takes to respond to an external event when an interrupt is triggered. Interrupt latency can be affected by several factors such as hardware, software, system load, and priority of the interrupt. A low interrupt latency is desirable in real-time systems as it allows the system to quickly respond to external events or time-critical tasks.

Using a Variable from Source File1 in Source File2

To use a variable defined in Source File1 in Source File2, you need to follow these steps:

1. Declare the variable in Source File2 using the keyword "extern" followed by the data type and variable name that was defined in Source File1. 2. Include Source File1 in Source File2 using the "#include" preprocessor directive. 3. Now you can access the variable in Source File2 and use its value as needed.

Here's an example of how this can be done in C:

Code:

In Source File1:


int num = 10;

In Source File2:


#include "file1.h"

extern int num;

int main()
{
   // Access num from Source File1 in Source File2
   int result = num + 5;
   
   return 0;
}

Note: Make sure to include the header file for Source File1 in Source File2 before using the `extern` keyword to declare the variable. This will ensure that the data type and variable name are consistent between the two files.

Understanding Segmentation Fault in Programming

A segmentation fault in programming refers to an error that occurs when a program attempts to access memory space outside of what has been allocated to it. This can happen due to a variety of reasons, such as trying to write to a read-only portion of the program's memory, attempting to access memory that has already been freed, or accessing uninitialized memory. Segmentation faults can be difficult to diagnose, but they often result in the program crashing or behaving abnormally. It is important for programmers to understand how to handle these errors in their code in order to create stable and reliable programs.

Differences between Inline and Macro functions

Inline functions:
- An inline function is a function that is expanded during compilation.
- The function call is replaced by the code of the function at compile time.
- It is used to improve runtime performance by reducing the overhead of function calls.

Macro functions:
- A macro function is a preprocessor directive that is expanded during preprocessing.
- The function call is replaced by the macro code before the code is compiled.
- It is used for text substitution and code generation.

Differences:
- Inline functions are written using the function keyword, whereas macro functions are written using the #define directive.
- Inline functions take arguments and return values like normal functions, whereas macro functions do not always have arguments or return values.
- Inline functions are type-checked by the compiler, whereas macro functions are not. This can lead to errors in macro functions if they are not used correctly.
- Inline functions can be used in object-oriented programming, whereas macro functions cannot.

The possibility of a variable being both volatile and const

Yes, it is possible for a variable to be both volatile and const. A volatile variable is a variable whose value may change at any time, without any action being taken by the code. A const variable, on the other hand, is a variable whose value cannot be changed once it has been set.

The combination of these two qualifiers may seem paradoxical, but it is useful in certain scenarios where a variable needs to be both read-only and subject to change outside of the control of the application.

An example of this could be a hardware register that is read-only from the perspective of the application code, but whose value may change due to external factors. In this case, the variable can be declared as both const and volatile, allowing the application to read the current value of the register while ensuring that the value cannot be changed by the code.

In conclusion, while it may seem contradictory at first glance, it is indeed possible for a variable to be both volatile and const in certain scenarios.

Can a Static Variable be Declared in a Header File?

Yes, it is possible to declare a static variable in a header file. However, doing so would result in each source file that includes the header file having its own copy of the static variable, rather than all source files sharing the same copy of the variable. This can lead to unexpected behavior and should be avoided. Instead, it is recommended to declare the static variable in a source file and use an extern declaration in the header file to make it accessible to other source files.

Understanding Pre-decrement and Post-decrement Operators in Programming

In programming, the pre-decrement and post-decrement operators are used to reduce the value of a variable by 1.

The pre-decrement operator (--var) subtracts 1 from the variable before it is used in the expression, while the post-decrement operator (var--) subtracts 1 from the variable after it is used in the expression.

For example, if we have a variable 'x' with a value of 5, using the pre-decrement operator like --x would result in 'x' being equal to 4. Using the post-decrement operator like x-- would still result in 'x' being equal to 4, but the value of 'x' would only be reduced to 4 after it has been used in the expression.

It's important to note that the position of the operators in the code can affect the outcome of the expression, and they should be used carefully and in the appropriate context.

What is a Reentrant Function?

A reentrant function is a type of function that can be safely called simultaneously by multiple processes or threads. This includes situations where a function is called recursively before the previous call has completed. To be considered reentrant, a function must use only local variables or variables passed as arguments, and it must not rely on global variables or other shared resources. Reentrant functions are important for ensuring thread safety in concurrent programming environments.

Embedded C Interview Questions for Experienced:

Question 16: Which type of loop is better: counting up from zero or counting down to zero?

It depends on the specific use case and the performance requirements of the system. In general, counting up from zero is slightly faster due to the way CPUs handle loop counters. However, if the loop involves accessing an array, counting down to zero may be faster since it can reduce the number of memory accesses. Ultimately, the most important factor is writing clean, readable and maintainable code.


// Example of counting up from zero
for (int i = 0; i < count; i++) {
   // Loop body
}

// Example of counting down to zero
for (int i = count - 1; i >= 0; i--) {
   // Loop body
}


Understanding Null Pointers in Embedded C

In embedded C, a null pointer is a pointer that does not point to any memory location. It is represented by the constant value '0'. A null pointer can be used as a sentinel value to indicate the end of a linked list or as an error code to indicate a failure to allocate memory. It is important to note that dereferencing a null pointer can result in a runtime error, so it should always be checked before use. Avoiding null pointer errors is critical for creating robust and reliable embedded systems.

Incomplete Declarations and Their Meanings

Code:


int num[];

The declaration "int num[]" creates an array of integers without specifying its size.


char letter;

The declaration "char letter" creates a single character variable named "letter".


float prices[];

The declaration "float prices[]" creates an array of floating-point numbers without specifying its size.


double amounts[10];

The declaration "double amounts[10]" creates an array of double-precision floating-point numbers with a size of 10 elements.


string names[5];

The declaration "string names[5]" creates an array of strings with a size of 5 elements. Note that "string" should be capitalized as "String".

Why is the statement ++i faster than i+1?

In programming, the ++i statement is faster than i+1 because the latter involves creating a new variable and performing an assignment operation. On the other hand, the ++i statement increments the value of i in place, without the need for a separate assignment operation. As a result, the ++i statement can be executed in a single step, while i+1 takes multiple steps, making it slower. Therefore, whenever possible, it is better to use ++i instead of i+1 for better performance.

Reasons for Segmentation Fault in Embedded C and How to Avoid Them

Segmentation faults, in embedded C, occur when a program tries to access a memory location that it is not allowed to access. This can happen due to several reasons, including:

1. Dereferencing a null pointer. 2. Accessing an array beyond its bounds. 3. Using uninitialized pointers. 4. Attempting to modify read-only memory. 5. Stack overflow.

To avoid these errors, you can follow some best practices while coding in C:

1. Always initialize variables before using them. 2. Ensure that pointers are not null before dereferencing them. 3. Perform boundary checking while working with arrays. 4. Use const keyword to define read-only memory. 5. Use dynamic memory allocation techniques like malloc() and free() for effective memory management. 6. Avoid using recursion as it can cause stack overflow. 7. Use a debugger to detect and fix errors.

By following these best practices, you can minimize the chances of encountering segmentation faults in your embedded C code.

Should printf() be used inside an ISR?

It is generally not recommended to use printf() inside an ISR (Interrupt Service Routine) as it can cause timing issues and potential data corruption. It is better to use other methods such as setting a flag to signal that an interrupt occurred and then handling the printing outside of the ISR. Additionally, printf() function can be a relatively long operation, which might take a significantly long time to execute, preventing the ISR to complete in a timely fashion. This could cause unforeseen consequences in the system and is therefore a bad practice.

Can a parameter be passed to an ISR or can a value be returned from it?

In general, Interrupt Service Routines (ISRs) are designed to be short and efficient, and do not typically involve passing arguments or returning values. Instead, ISRs are triggered by hardware events, such as a timer or a button press, and execute a specific set of instructions in response to that event.

That being said, it is possible to provide some limited context to an ISR by making use of global variables or other shared resources. For example, if a button press triggers an ISR, the ISR may be responsible for updating a shared variable that tracks the state of the button.

In summary, while passing arguments or returning values from an ISR is not a common practice, it may be possible to achieve similar functionality through the use of shared resources.

Virtual Memory in Embedded C

Virtual memory is a memory management technique that allows a computer to use more memory than physically available by temporarily transferring pages of data from RAM to a disk. This can be very useful in embedded systems where there may not be enough physical memory to store all necessary data.

To implement virtual memory in embedded C, you can use a memory management unit (MMU) and paging scheme to manage memory. The MMU maps virtual addresses to physical addresses, while the paging scheme allows for the efficient transfer of data between physical memory and disk storage.

However, it's important to note that implementing virtual memory in embedded systems can be complex and may require significant resources. It's important to carefully consider whether virtual memory is the best solution for your specific embedded system before implementing it.I'm sorry, there is no code provided to identify any issues with it. Please provide the code in question.

Code Review: Using Interrupt Keyword to Define an ISR

The provided code is not shown, so it's impossible to comment on its correctness. However, based on the given information, it seems that the code uses the "__interrupt" keyword to define an ISR (Interrupt Service Routine).

In general, using the "__interrupt" keyword to define ISRs is a correct approach in many microcontroller systems. However, the correctness of the code depends on how the ISR is implemented and what it is used for.

It is also important to ensure that the ISR handles the interrupt in a timely and efficient manner, without affecting the overall system performance. The code should be well-structured and easy to read, with proper comments and documentation.

Overall, it is crucial to carefully design and test ISRs to ensure that they work correctly and reliably in the target system.I'm sorry, but there is no code provided for me to analyze and provide a result. Can you please provide the code so I can assist you better?

Reasons for Interrupt Latency and Strategies to Reduce It

Interrupt latency refers to the time delay between the occurrence of an interrupt and the start of its processing. This delay can be caused by several factors, including:

1. Processor Performance: A slow processor or one that is already processing a high volume of tasks may take longer to switch to an interrupt context.

2. Interrupt Prioritization: If there are multiple interrupt requests, the CPU or the operating system must prioritize them and assign them an order for processing. This can contribute to latency.

3. Shared Resources: If multiple devices or processes must share the same resources, such as memory or I/O channels, conflicts can arise that slow down the processing of interrupts.

To reduce interrupt latency, it is essential to employ strategies such as:

1. Prioritizing CPU Performance: Use a processor with a suitable architecture and clock speed that can handle the volume of tasks required by the system's workload.

2. Efficient Interrupt Handling: Ensure that the operating system can efficiently handle interrupts by optimizing its interrupt processing algorithms, particularly in regard to prioritization.

3. Resource Allocation: Allocate system resources efficiently, allowing for the sharing of resources where necessary, but minimizing resource conflicts that can add to interrupt latency.

By employing these strategies, it is possible to reduce interrupt latency and improve system performance.

Protecting a Character Pointer from Accidentally Pointing to a Different Address

Yes, it is possible to protect a character pointer from accidentally pointing to a different address. One way to do so is by using the const keyword when declaring the pointer. This will make the pointer read-only, and any attempt to modify the value it points to will result in a compilation error.

For example:


const char* str = "Hello";

This declares a character pointer variable str that points to the string "Hello". Since the const keyword is used, the string pointed to by str cannot be modified.

Another way to protect a character pointer is by setting it to NULL after freeing the memory it points to. This will prevent the pointer from pointing to a potentially invalid or dangerous memory address.

For example:


char* str = (char*) malloc(sizeof(char) * 10);
// Do some operations on str
free(str);
str = NULL; // Now str is a null pointer

This code declares a character pointer variable str, allocates 10 bytes of memory for it using malloc(), performs some operations on the memory, frees the memory using free(), and sets the pointer to NULL to prevent any accidental access to the freed memory.

By using these techniques, you can ensure that your character pointers are safe from accidental pointing to different addresses and prevent undefined behavior in your program.

Understanding Wild Pointers and Dangling Pointers

In C programming, a pointer is a variable that holds the memory address of another variable. A wild pointer refers to a pointer that has not been initialized or assigned any value, thus pointing to a random memory address that may or may not be valid. This means that the pointer can lead to unexpected behavior or program crashes.

On the other hand, a dangling pointer refers to a pointer that used to point to a valid memory address, but the memory has been freed or deallocated, making the pointer invalid or dangling. This means that the pointer can also lead to unexpected behavior or program crashes if used further.

To avoid such scenarios, it is important to always initialize pointers to a valid memory address and avoid accessing or using dangling pointers.

Differences between #include "..." and #include ?

In C and C++ programming languages, the "#include" preprocessor directive is used to include header files into the source code. There are two ways to include header files:

1. Using #include "header.h" - This tells the preprocessor to look for the header file in the current directory first, and then look for it in the standard library directories.

2. Using #include - This tells the preprocessor to look for the header file only in the standard library directories.

So, the main difference is in the search path – #include "header.h" searches in the current directory before looking for it in the standard library directories, while #include searches only in the standard library directories.

Memory Leaks: When do they occur and how to avoid them?

Memory leaks happen when a program fails to free up unused memory, causing the system to slow down and eventually crash. Memory leaks may occur due to a variety of reasons such as programming errors, unhandled exceptions, or infinite loops.

Here are some ways to avoid memory leaks:

1. Use smart pointers instead of raw pointers to avoid manual memory allocation and deallocation. 2. Avoid global variables and instead use local variables or class members. 3. Implement exception handling to prevent unhandled exceptions from causing memory leaks. 4. Always free memory once it is no longer needed. 5. Use memory leak detection tools to identify and fix any potential memory leaks.

By following these practices, you can help prevent memory leaks and ensure that your program runs smoothly and efficiently.

Embedded C Program to Multiply Any Number by 9


#include <stdio.h>

int main() {
    int num = 0;
    printf("Enter a number: ");
    scanf("%d", &num); //read input number
    
    int result = (num << 3) + num; //multiply by 9 using bitwise shift and addition
    printf("Result: %d", result); //print result
    
    return 0;
}

In this program, we take the input number from the user and multiple it by 9 in the fastest manner using bitwise shift and addition. By left-shifting the input number by 3 bits (which is equivalent to multiplying by 8) and then adding the original input number, we get the result of multiplying the input number by 9 in a quick and efficient manner.

Swapping Two Variables in Different Approaches

There are different methods to swap the values of two variables. Here are three common approaches:

// First Approach: Using a temporary variable int temp; temp = variable1; variable1 = variable2; variable2 = temp;

// Second Approach: Using Addition and Subtraction variable1 = variable1 + variable2; variable2 = variable1 - variable2; variable1 = variable1 - variable2;

// Third Approach: Using the XOR operator variable1 = variable1 ^ variable2; variable2 = variable1 ^ variable2; variable1 = variable1 ^ variable2;

The first approach involves using a temporary variable to store one of the values, and then swapping the values between the two variables.

The second approach involves using addition and subtraction to swap the values. First, add the values of both variables and store the sum in the first variable. Then, subtract the value of the second variable from the first variable, and store the difference in the second variable. Finally, subtract the value of the second variable from the new value of the first variable to get the original value of the second variable.

The third approach makes use of the XOR operator to swap values. XOR operation sets each bit to 1 if only one of two bits is 1. First, perform XOR operation between the values of both variables and store the result in the first variable. Then, perform XOR operation between the values of first and second variables, and store the result in the second variable. Finally, perform XOR operation between the new value of first variable and the new value of the second variable to get the original value of the first variable.

Check If a Number is a Power of 2 or Not


    def is_power_of_two(number):
        """
        This function checks if a given number is a power of 2 or not.
        """
        # A number that is a power of 2 will only have 1 bit set.
        # For example, 2 (10 in binary) and 8 (1000 in binary) are powers of 2
        # whereas 3 (11 in binary) and 9 (1001 in binary) are not.
        if number == 0:
            return False
        while number != 1:
            if number % 2 != 0:
                return False
            number = number // 2
        return True

    # Test the function with some examples
    print(is_power_of_two(0))   # False
    print(is_power_of_two(1))   # True
    print(is_power_of_two(2))   # True
    print(is_power_of_two(3))   # False
    print(is_power_of_two(8))   # True
    print(is_power_of_two(10))  # False


Printing Numbers from 1 to 100 without Conditional Operators


    public class PrintNumbers {
        public static void main(String[] args) {
            int num = 1;
            while (num <= 100) {
                System.out.println(num);
                num += 1;
                num += num / 101; // effectively adding 1 if num reaches 100
            }
        }
    }

In this program, we use a while loop to increment the number by 1 and print it until we reach 100. We avoid using any conditional operators such as if or switch by adding 1 to the number when it reaches 100. This is achieved by the line

num += num / 101;

Min Macro Program


  #define MIN(x,y) ((x) < (y) ? (x) : (y))

The above macro named "MIN" takes two arguments "x" and "y", and returns the smallest of the two arguments. It does this by comparing the values of "x" and "y" using the ternary operator "?". If "x" is less than "y", then it returns "x". Otherwise, it returns "y".

Technical Interview Guides

Here are guides for technical interviews, categorized from introductory to advanced levels.

View All

Best MCQ

As part of their written examination, numerous tech companies necessitate candidates to complete multiple-choice questions (MCQs) assessing their technical aptitude.

View MCQ's
Made with love
This website uses cookies to make IQCode work for you. By using this site, you agree to our cookie policy

Welcome Back!

Sign up to unlock all of IQCode features:
  • Test your skills and track progress
  • Engage in comprehensive interactive courses
  • Commit to daily skill-enhancing challenges
  • Solve practical, real-world issues
  • Share your insights and learnings
Create an account
Sign in
Recover lost password
Or log in with

Create a Free Account

Sign up to unlock all of IQCode features:
  • Test your skills and track progress
  • Engage in comprehensive interactive courses
  • Commit to daily skill-enhancing challenges
  • Solve practical, real-world issues
  • Share your insights and learnings
Create an account
Sign up
Or sign up with
By signing up, you agree to the Terms and Conditions and Privacy Policy. You also agree to receive product-related marketing emails from IQCode, which you can unsubscribe from at any time.