0% found this document useful (0 votes)
36 views

C Language Topics For Interview

The document discusses the different stages of C program compilation: 1. Pre-Processing stage where header files are expanded and macros are replaced. 2. Compilation/Translation stage where syntax and semantics are analyzed and assembly code is generated. 3. Assembly stage where assembly code is converted to machine readable code. 4. Linking stage where internal/external functions are linked and an executable file is generated.

Uploaded by

Srikanth Reddy
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
36 views

C Language Topics For Interview

The document discusses the different stages of C program compilation: 1. Pre-Processing stage where header files are expanded and macros are replaced. 2. Compilation/Translation stage where syntax and semantics are analyzed and assembly code is generated. 3. Assembly stage where assembly code is converted to machine readable code. 4. Linking stage where internal/external functions are linked and an executable file is generated.

Uploaded by

Srikanth Reddy
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 24

C - PROGRAMMING

INTERVIEW PREPARATION GUIDE


C PROGRAM COMPILATION STAGES:

1. Pre-Processing stage ( .c  .i )
• Header files expanded
• Macros will get replaced
• Specific code will be included based on conditional macro’s
• Comments will be removed

2. Compilation/Translation stage ( .i  .s )
• Syntax and semantics analyses will be done
• Reports errors if any which will help programmer to fix it.
• Generates assembly code

3. Assembly stage ( .s  .o )
• Assembly code converted into machine readable code.

4. Linking stage ( .o  .exe/.out )


• Internal/External functions linkage will be done
• Generates the architecture dependent executable file to execute
STORAGE CLASSES C Memory Layout
S.No. Storage Class Storage Initial Scope of the Life of the
Name value variable variable
1 auto Stack Garbage Within function / Within function / STACK
Section Block Block
Mapped
2 register CPU Garbage Within function / Within function / to
Registers / Block Block HEAP
RAM
Stack
Section .data

DATA .bss
3 local static Data Section 0 Within function / Till program .rodata
Block exists Mapped
global static Data Section 0 Within a file only Till program to
exists Flash CODE

4 extern Data Section 0 Throughout a Till program


program exists

 Cannot declare static member as a structure variable


 Structure variables can be declared as static Initialized variables Uninitialized variables
 Functions can be declared as static
 Its possible to declare const variable within a structure but String, assigned global const
needs to be initialized as soon as you declare variable for the variables will be stored here.
static const behaviour is undefined
structure.
Modifiers
• short
• long
• unsigned
• signed

Qualifiers
• const
 Constant variable gets stored in different sections of the memory based on how you are declaring:
 auto const variable will be stored in stack section and using pointer you can modify it.
 Uninitialized global const variables will be stored in .bss section and using pointers you can modify it.
 Assigned global const variables will be stored in RODATA section and using pointers you cannot modify it, it
leads to SIGFAULT or crash.
 Static const variable behaviour is undefined.
 Strings also gets stored in RODATA section.
• volatile
 Assume that there is a TEMP sensor which will sense the temperature data and its driver can read the data and write
into some shared memory and there is an application which will display the whether condition on the screen by
reading the data from that same shared memory.
 These are 2 different programs and in application your program only reads this memory value so compiler will
optimizes it to get better performance but we need always updated value from memory so to avoid optimization
we will use volatile.
 const volatile is also possible as we are not modifying the variable in that program.
Memory Corruptions
Global Memory Corruption
A global data can be corrupted due to Array index overflow from the previous data or Array index underflow from
the next data.
Uninitialized pointers can also cause this issue.

To debug:
• Put a write break point and debug it.
• If no debug setup available then browse through the code to find if any previous or next data causing this.
• Or else looking at the patterns what has written in your global location can also give you an idea.

Heap Memory Corruption


Corruption on the heap can be very hard to detect. A heap corruption could lead to a crash in heap management
primitives that are invoked by memory management functions like malloc and free.

Reasons:
• If a crash is observed in memory management primitives of the operating system, heap corruption is a possibility. It has
been observed that memory buffer corruption sometimes leads to corruption of OS buffer linked list, causing crashes on
OS code.
• If a memory corruption is observed in an allocated buffer, check the buffers in the vicinity of this buffer to look for source
of corruption.
• Corruption of buffers close to heap boundary might be due to stack overflow or stack overwrite leading to heap
corruption Conversely, stack corruption might take place if a write into the heap overflows and corrupts the stack area.
Stack Memory Corruption
Stack corruption is a phenomenon in which some memory locations at stack are accessed unintentionally due to wrong
coding leading to change in values at those memory locations. Since the data corruption happens on stack memory
locations, hence the term Stack Corruption.

Reasons :
• Due to bad written code, all the stack memory gets eaten up (like infinite function call)
• Accessing array out of bounds.
• An undefined/freed pointer pointing or storing a garbage stack address.
• When due to some reason, the return address of a function call gets corrupted.

Dynamic Memory Use Cases

In most of the compilers, some extra bytes will be allocated for any dynamic memory allocations for book-keeping
information like SIZE of the allocation, etc.

Extra bytes for header Actual requested SIZE

1) malloc( 0 ) returns atleast small chunk of memory (16B for example) which needs to be freed later.
2) malloc(-ve value) will try to allocate huge block and fails if memory is not available because malloc takes unsigned value
and –ve value rounded to huge positive value.
3) free(ptr) looks for small chunk to find out SIZE of the allocated memory and frees it.
4) realloc (ptr, 0) also works as malloc (0).
Note:
1) Size of a pointer is equal to size of ARCH-bit/8.
For example, for 64-bit machine, 64/8 = 8 bytes and for 32-bit, 32/8 = 4 bytes.

2) How to know the STACK is growing up/down?

main()
{
char x[2];

if((&x[1] - &x[0]) < 0 )


printf(“DOWN STACK\n”);
else
printf(“UP STACK\n”);
}

3) sizeof operator implementation:

#define SIZEOF(X) ({__typeof__(X) Y; ((char*)(&Y+1) - (char*)(&Y));})

4) Container of a structure variable:

#define CONTAINER_OF(ptr, type, member) ((type*) ((char*)ptr - (char*)&(((type*)0)->member)))


STRUCTURE:
Structure is an user defined data type which will keeps all the related data members of an entity into a single container.

For example PROCESS is an entity and its structure can be defined as below:

struct process
{ without struct padding With struct padding
uint32_t process_no; x0 x0
uint32_t pp_no;
struct process *child_processes; x1 x1
… x2 x2
};
x3 x3
STRUCTURE PADDING: y0 y0
By default any processor access the memory word by word.
For example, on 32-bit ARM processor memory will be accessed 4 bytes (1 word) at a time. z0 0
ARM
z1 0
struct xyz
{ z2 0
int x; z3 z0
char y;
float z; z1
}; z2
z3
Bitfields:
Bitfields are used in the scenario where we use flag in our code and for that no need to waste lot of memory instead we
can use few bits from the byte or word. We can save the memory with bitfields.

Declaration:
struct bitfield
{
char flag1:1;
char flag2:4;
}b;

To access them, we need to do bit-wise operations:


b.flag1 = 1;
b.flag2 = 0xC;

printf(“ %d %d \n”, (b.flag1 & 1), (b.flag2 & 0xF);

Typedef:
Using typedef we can give alias name to the existing/user defined data type to increase the readability and simplicity.

typedef struct linkedlist list;

list *Node;
UNIONS:
Unions will be used in the scenario where there are multiple members needed in the application for a same entity but all
the members are mutual exclusion.
All the members share the common memory area where highest no. of bytes will be allocated for that union.
Ex:
union xyz
{
char x; x0/y0 y1 y2 y3
int y;
}u;
Enumerations or enums:
Enums are mainly used to assign names to integral constants, the names make a program easy to read and maintain.
enum week
{
Mon,
Tue,
Wed
..
}day;

day = Wed;  it means day = 2 as enums value starts from 0, 1, 2, etc…


Arrays and Pointers
• Pointers can hold the address of another variable or another pointer.
• On pointer, we can do addition/subtraction/increment/decrement operations but not any other. It is meaningful to do these
operations only on continuous memory locations.
• With pointers we can access 1-D array memories but cannot access 2-D array memory by using pointer to pointer.
• Array increment/decrement cannot be performed as array is constant pointer.

Combination of pointer and const :

char *p;  p is a pointer which will point to char variable


const char *p;  p is a pointer which will point to const char variable
char *const p;  p is a constant pointer which will point to char variable
const char *const p;  p is a constant pointer which will point to const char variable

int *p; int **q; int ***ptr;

Arrays of Pointers:
int *p[5] = { &x, &y, arr, &m, &n}

Pointer to Array:
int (*q)[5] = arr;  Where arr is int arr[5]; array size must be 5 only.
int a[2][5] = { {1, 2, 3, 4, 5}, {11, 22, 33, 44, 55}};

int *ptr = (int*)a;

int (*ptr2)[5] = a;

printf(“%d %d\n”, *ptr, *(ptr + 1)); 1 2

printf(“%d %d\n”, **ptr2, **(ptr2 + 1));  1 11

Below method will not give correct result using pointer to pointer

int **ptr1 = (int **)arr;

printf(“%d %d\n”, *ptr1, *(ptr1 + 1)); 1 3


If you assign string to char
===== This is stored in RODATA section
array then that whole string
char *p = “Hello”; will be copied into the array.
This is stored in .data section So, you can modify that
int main( void ) string.
{ This is stored in RODATA section Ex: char str[10]=“Sat”;
char *q = “World”; *str[0] = ‘M’;
This is stored in stack section
}
Operations performed during function call:
• Push calling functions register values into the stack frame
• Update the link register(LR) with the return address
• Update the general purpose registers(R0-R4 based on compiler and system) with the actual values of the called
functions.
• Make a function call and create its stack frame.
• Push formal arguments to the stack frame from registers.
• Do the operations and return back using the address stored in LR.
• Reload the registers from stack frame and do the normal operations.

INLINE function:
Inline function must be declared with static keyword.

Ex:
static inline func(void)
{

}
Diff b/w macro’s and inline functions:
• Macro’s will be expanded in preprocessing stage and inline will be replaced in a code during compilation stage.
• Macros will not do any type checking but inline functions will do it.

Write a macro to find out the biggest among 2 different numbers. Also macro to find out the largest among 3 different
numbers.
Which one is good normal function or inline function:
• Its based on the requirement.
• Normal function always creates stack frame and destroys it once done and lot of other operation takes place which will
consume lot of CPU cycles. And it won’t give better performance compared to inline functions.
• But, with normal function call memory footprint will be less.
• Where as inline function will be expanded during compilation itself so memory footprint will be more.
• But, it gives better performance as there is no CPU cycles wasted during execution.

Note: Formal parameter of the functions can be declared with register storage class only.

Bit-wise operations:
Set bit : n = n | ( 1 << bit ) System is Little endian or big endian

Reset bit: n = n & ~ ( 1 << bit ) int x = 0x1;

Toggle bit: n = n ^ ( 1 << bit ) if((char)x & 1)


printf("Little Endian\n");
Check if kth bit is set or reset: if( n & (1 << k) != 0) then set else reset else
printf("Big Endian\n");
Nibble swap in a byte: n = (n & 0xF0 >> 4) | (n & 0x0F << 4)

Little to big endian or vice-versa(32-bits):

n = ( n & 0xFF000000 >> 24) | ( n & 0x000000FF << 24) | ( n & 0x00FF0000 >> 8) | ( n & 0x0000FF00 << 8)
• Given no. is power of 2 or not : if(n & (n – 1) == 0) then power of 2
• Given no. is power of 4 or not: (1) it should be power of 2 .
(2) c = 0; while((n & 1) == 0) { c++; n >>= 1;} if( n == 1 || (c && c%2 == 0)) then power of 4
• Given no. is even or odd: if(n&1 == 0) then EVEN else ODD
• Reverse the bits of a given number: for(i = 0, j = 31; i<j; i++, j--) { if( !(n & ( 1 << i)) != !(n & (1 << j))) then toggle both bits
• Check if the given no. is –ve : if((n >> 31) & 1 ) == 1) then –ve
• Add 1 to the given no. : x = -(~x);
• Swap 2 no. using bit-wise: x = x^y; y = x^y; x = x^y;

Explore on below questions:


• Difference b/w strcpy/memcpy/memmove
• Swap 2 variables without 3rd variable (x = x+y; y= x-y; x = x-y;)
• Implement all the string functions like atoi/strcat/strchr/strcmp/strcpy/strlen/strncat/strncmp/strncpy/strlcpy/
strlcat/strrchr/strstr
• Implement sorting algorithms ( Bubble sort/Selection sort/Insertion sort/Quick sort/Merge sort)
• Implement searching algorithms ( Linear/Binary search)
• Implement binary to decimal
• Implement decimal to binary (-ve also)
• Convert little-endian to big-endian by using UNION

Note: Solve as many as questions from


https://blog.usejournal.com/500-data-structures-and-algorithms-practice-problems-35afe8a1e222?gi=fbe293767db9
DATA STRUCTURE
STACKS
A stack is a linear data structure, collection of items of the same type.
Stack follows the Last In First Out (LIFO) fashion wherein the last element entered is the first one to be popped out.

The following are the basic operations served by the Stacks.


• IsEmpty: Checks whether the stack is empty.
• IsFull: Checks whether the stack is full.
• Push: This function adds an element to the top of the Stack.
Algo:
PUSH
begin
check IsFull
Cannot push elements & return
push element into the top of the stack
end
• Pop: This function removes the topmost element from the stack.
Algo:
POP
begin
check IsEmpty
Cannot pop elements from the stack & return
pop the elements from the top of the stack
end
• Pip: Displays the topmost element of the stack.
Algo:
PIP
if not empty return the top elements
QUEUE
A Queue is a linear data structure which follows a particular order in which the operations are performed.
The order is First In First Out (FIFO).

The following are the basic operations served by the Stacks.


• Insert: This function adds elements into the queue.
Algo:
INSERT
begin
check if Queue is full
Cannot insert the elements & return
insert the elements into the queue at rear end
end

• Delete: This function deletes elements from the queue.


Algo:
DELETE
begin
check if Queue is empty
Cannot delete elements from the queue
Remove element from the queue at front end
end
Single Linked List:
A linked list is a linear data structure, in which the elements are not stored at contiguous memory locations.

Declaration of single linked list:


struct list
{ NODE
int data;
struct list *next; Data Next
};
Head
Data Next Data Next Data Next Data Next Data Next

Struct declaration for generic linked list:


• Insert the element at the beginning of the list struct list
• Insert the element in the middle of the list {
• Insert the element at the end of the list void *data;
• Delete the first node from the list struct list *next;
• Delete the middle node from the list }
• Delete the last node from the list
Questions on the linked list, stacks and queues:
1. Reverse the single linked list
2. Reverse the double linked list
3. How do you reverse a linked list without using any C pointers?
4. Given only a pointer to a node to be deleted in a singly linked list, how do you delete it?
5. How would you detect a loop in a linked list?
6. How do you find the middle of a linked list?
7. Write a C program to return the nth node from the end of a linked list
8. Implement stack using linked list
9. Implement queue using linked list
10. Implement queue using stack
11. Find the common node between the 2 lists.
12. Implement Queue by using Stack.
13. Addition of numbers from 2 lists and strong the results in new list.

File Operations:
14. Creating a file, reading, writing a file
15. Reading files from directory recursively and searching a particular string in it
16. Print the count of that string present in each file and total count in the directory.
Libraries
• A library in C is a collection of object
files exposed for use and build other
programs, so instead of re-write
sections of code we bring back
information that is already existing
through a library.
• There are 2 different kinds of libraries:
• Static libraries that allows us to link to
the program during compilation
• Dynamic or shared libraries those are
preferable use when you run a lot of
programs at the same time who are
using the same library.
How to create static library?
$gcc –c file1.c file2.c
The flag '-c' we use it to stop the compilation process till compilation and assembly stage but not
done the linker phase so in this case we created a new file call 'file1.o‘ and 'file2.o‘.

$ar rc lib_file.a file1.o file2.o


• It is recommended to use rc flag because 'c' allow to create the libraries if it doesn’t already
exist and 'r' to replace the older object for the new ones.

$gcc test.c –L. -l_file -o test


$gcc test.c –L/mnt/d/Code_WS/static_libs -l_file -o test
• With this code we compile the c program and creating and execute name newfile, the meaning,
gcc: Compile the file.c, -L: Search the direction of the file ‘.’ mean current directory, -l: To use
the library, -o: To create and executable name newfile.

• $./test
Dynamic library?
Dynamic libraries (also called shared libraries) are linked into the program in two stages.
• First, during compile time, the linker verifies that all the symbols required by the program, are either linked
into the program or in one of its dynamic libraries. However, the object files from the dynamic library are
not inserted into the executable file.
• Instead, when the program is started, a program in the system (called a dynamic loader) checks out which
shared libraries were linked with the program, loads them to memory, and attaches them to the copy of the
program in memory.

How to Create Dynamic Libraries?


It will be done in 2 stages:
• Compile for "Position Independent Code" (PIC):
When the object files are generated, we have no idea where in memory they will be inserted in a program
that will use them. Many different programs may use the same library, and each loads it into a different
memory in address. We need that all jump calls and subroutine calls will use relative addresses, and not
absolute addresses. Thus, we need to use a compiler flag that will cause this type of code to be generated.
• $gcc -fPIC -c file1.c file2.c file3.c
With this command, we will create a list of object files with the names of file1.o, file2.o and file3.o.
• Library File Creation:
Unlike a static library, a shared library is not an archive file. It has a format that is specific to the
architecture for which it is being created. Thus, we need to use the compiler (either the compiler's driver
or its linker) to generate the library and tell it that it should create a shared library, not a final program file.

$gcc -shared -o lib_name.so file1.o file2.o file3.o

How to use them (Linux only)

$gcc main.c -L. -l_name -o prog

./prog
./test: error while loading shared libraries: lib_dynlib.so: cannot open shared object file: No such file or
directory
$export LD_LIBRARY_PATH=/mnt/d/Code_WS/dynamic_libs

$./prog
THANK YOU…

You might also like