2011年1月8日 星期六

Dynamic Memory Allocation

In a software or firmware interview, you may be asked about dynamic memory allocation in different way.


What is dynamically memory allocation?
dynamic memory allocation (heap-based memory allocation) is the allocation of memory storage during the runtime of that program. Dynamically allocated memory exists until it is released either explicitly by the programmer. It is said that this object has a dynamic lifetime.


How to dynamically allocate memory in C?
malloc() provides dynamic memory allocation.

    void *  malloc (  size_t  size )

The type of returned pointer is *void since malloc doesn't know how you are going to use the allocated memory, so you need to cast it before using.


Memory Leak
When you don't need the allocated memory anymore, you must free it by calling free(). If you forgot to do it, the allocated memory will be always reserved and the system cannot use it, it is so called memory leak


free() releases the allocated memory.
    void  free (  void *p )
Example
        int *pA ;
        pA = ( int *) malloc (sizeof (int) * 10 ) ;  //
allocate 10 int ( 40 bytes )
        //use the allocated memory here
        free(pA);   //release the memory


Where is the dynamically allocated memory located?
The memory allocated by using malloc will be located in heap section.

Function Declaration

main()
{
    char string[]="Hello World";
    display(string);
}
void display(char *string)
{
    printf("%s",string);
}
Compiler will issue an error or warning to the above code. Because there is no function prototype, compiler will assume "int display(int)" for display()'s prototype. Therefore, there is a prototype conflicting.

C Language Operator Priority

Operators
Associativity
() [] -> .
 left to right
! ~ ++ -- + - * (type) sizeof
 right to left
* / % 
 left to right
+ - 
 left to right
<<  >> 
 left to right
< <= > >= 
 left to right
== != 
 left to right
& 
 left to right
^ 
 left to right
| 
 left to right
&&
 left to right
|| 
 left to right
?: 
 right to left
=  +=  -=  *=  /=  %=  &=  ^=  |=  <<=  >>=    
right to left
, 
 left to right

Type Conversion

Type conversion is a frequently asked concept in any software/firmware interview.


Rules
  • assignment: right type converts to left type, with or without sign expression
  • compare & binary operator: converts low to high (unsigned higher than signed)
Char to Int
char c = 0xFF;
int i = c;
cout << i;
It's machine dependent. There are two situations. (without sign extension)
1) It's always converted to a positive number.
255
2) It can be both positive or negative number, and depend on MSB of c. (with sign extension)
-1
c = 0xFF = -1 —-> i = -1 = 0xFFFF

Int to Char
directly truncate

Pointer Conversion
int i = 10;
char *c;
c = &i;
-> convert pointer of int to pointer of char
-> i + 1 = i + sizeof(int)
-> c + 1 = c + sizeof(char)

Function Declaration

main()
{
    char string[]="Hello World";
    display(string);
}
void display(char *string)
{
    printf("%s",string);
}
Compiler will issue an error or warning to the above code. Because there is no function prototype, compiler will assume "int display(int)" for display()'s prototype. Therefore, there is a prototype conflicting.

Data Declaration

The declaration of an array and pointer is very important and a good c programmer must know it well, so interviewer often ask about this concept.


Using the variable a, give definitions for the following:
a) An integer
b) A pointer to an integer
c) A pointer to a pointer to an integer
d) An array of 10 integers
e) An array of 10 pointers to integers
f) A pointer to an array of 10 integers
g) A pointer to a function that takes an integer as an argument and returns an integer
h) An array of ten pointers to functions that take an integer argument and return an integer

The answers are:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int * *a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer

Memory Allocation Sections

The concept of memory allocation section is very important for a firmware engineer.


Stack
Stores automatic variable such as local variable, function parameters and function return address. Data stored here is automatically allocated and released in execution time.

Heap
Stores data that is dynamically allocated by programmer using function such as malloc() and calloc() (in C++ we use "new" operator). Operating system might or might not release the memory after the program exit.
p1 = (char*)malloc(10);
p2 = new char[10];

Allocate char array with size of 10 in heap section. Be careful, p1 and p2 themselves are automatic variable stored in stack section.
Disadvantage:
(1) memory leak
(2) it can cause external fragmentation, especially when we dynamically allocate variables with various size


Static
Stores global variable and static variable. This section can be further separated to Data section and Bss section.
Data section stores initialized global and static variable, while Bss section stores uninitialized global and static variable.

String Constant
Stores string constant. Memory is released after program exit.

Text
Stores binary codes.

Volatile

Almost every firmware interviewer will ask you about volatile variable. If you don't understand it, then you must read it carefully now!!!


A variable should be declared volatile whenever its value could change unexpectedly. In practice, only three types of variables could change:
1) A pointer points to a memory mapped hardware registers (for example, status registers)
2) Non-automatic variables which can be modified by an interrupt service routine
3) Shared variables within a multi-threaded application
Ex.
UINT1 * ptr = (UINT1 *) 0x1234;

// Wait for register to become non-zero.
while (*ptr == 0);
// Do something else.

should be changed to
UINT1 volatile * ptr = (UINT1 volatile *) 0x1234;

the meaning is: ptr point to a variable that may be unexpectedly modified, so every time when we deference it(*ptr), we need to actually retrieve data from its memory address.

Reference

Typedef

Typedef in C language will be heavily used in firmware code. If you don't know it clearly, sometimes it is even difficult to understand the interview questions!

typedef vs macro

#define dPS struct s *
typedef struct s * tPS;

The intent in both cases is to define dPS and tPS to be pointers to structure s. 
Which method, if any, is preferred and why?
This is a very subtle question, and anyone who gets it right (for the right reason) is to be congratulated or condemned ("get a life" springs to mind). 
The answer is the typedef is preferred. Consider the declarations:

dPS p1,p2;
tPS p3,p4;

The first expands to:
struct s * p1, p2;

which defines p1 to be a pointer to the structure and p2 to be an actual structure, which is probably not what you wanted. 
The second example correctly defines p3 and p4 to be pointers.

Macro

Like typedef, macro will be heavily used in firmware code, and you will encounter it in almost every firmware interview.

Example

#define FIND(x,a)      (void*)&x.a - (void*)&x       //return the offset of the member a in the structure x

 
struct test {
      int i; 
      char c;
};
 
int main()
{
      struct test t;
      printf("%d\n", FIND(t,c));
      return 0;
}

Note

1) #define FIND( x , a ) (void*) &x.a - (void*) &x //still correct.

2) #define FIND (x,a) (void*)&x.a - (void*)&x //WRONG!!

Macro vs Inline

Although inline functions are similar to macros (because the function code is expanded at the point of the call at compile time), inline functions are parsed by the compiler, whereas macros are expanded by the preprocessor. As a result, there are several important differences:

1) Inline functions follow all the protocols of type safety enforced on normal functions.
Consider the example below which depicts the disadvantage of the lack of type checking. In this example, the result is always the value passed in as the second argument.
#include "iostream.h"
 
#define MAX(a, b)              ((a < b) ? b : a)
 
int main( void)
{
    cout << "Maximum of 10 and 20 is " << MAX("20", "10") << endl;
    return 0;
}
Output:
Maximum of 10 and 20 is 10
2) Inline functions are specified using the same syntax as any other function except that they include the inline keyword in the function declaration.
3) Expressions passed as arguments to inline functions are evaluated once. In some cases, expressions passed as arguments to macros can be evaluated more than once.

Sizeof

sizeof
struct {
    int i;
    char c1;
    char c2;
} s1;
struct {
    char c1;
    int i;
    char c2;
} s2;
struct {
    char c1;
    char c2;
    char c3;
} s3;
struct {
    double d;
    char c1;
    char c2;
} s4;
int main()
{
char* cp = "abcdef";
printf("%d\n", sizeof(cp));             
//pointer to char -> 4

char ca[] = "abcdef";
printf("%d\n", sizeof(ca));             
//abcdef + '\0" -> 7

char ca2[100] = "abcdef" ;
printf("%d\n", sizeof(ca2));           
//100

int ia[100];
printf("%d\n", sizeof(ia));              
//100 * 4 -> 400

char* cp2 = (char*) sizeof(100);       
printf("%d\n", sizeof(cp2));              
//4     
printf("%d\n", sizeof(s1));             
//8
printf("%d\n", sizeof(s2));             
//12      
printf("%d\n", sizeof(s3));             
//3        
printf("%d\n", sizeof(s4));             
//12   
return 0;
}
char f() {
return 'c';
}
int main()
{
int a = 0;
printf("%d\n", sizeof(a++) );         
// sizeof integer -> 4
printf("%d\n", a);                            
// 0    (instruction in sizeof won't be executed)
printf("%d\n", sizeof(f()) );            
// 1    (sizeof(function()) returns the size of the return type of the function)
printf("%d\n", sizeof(f) );               
// 1    (same)
}


sizeof vs strlen
#include <stdio.h>
#include <string.h>

int main()
{
    char *m = "abcde";
    printf("%d\n", strlen(m));

    char r[] = "abcde";
    printf("%d\n", sizeof(r));
}
Output:
5
6