October 29, 2011

Static/global variable and shared library behaviour

[Note : This would be a very long discussion and reader would need considerable amount of time to complete the reading]

Problem Description
Let's take an example of a class having a static variable or a global variable.  

Header file : x.h
extern int g_s;

class XYZ
{
        static int s;
    
    private:
        static int getInt();
};
Implementation file:x.cpp


int XYZ::s = 777;
int g_s = 7;
int XYZ::getInt()
{
  return s;
}

Now let us go through below questions (Little Quiz) and answer them
  • What is the scope of global variable (g_s) and static class variable (XYZ::s)? 
    • Naturally, the answer would be global. 
    • For class static data member variable, the scope would be global with additional limitation of privilage (i.e all objects belonging to XYZ in the executable will have only one copy of s and only accessible for class interface).
  • How many copies of the variable exists? 
    • One 
Are the above answers really correct? What happens in case of executable involving multiple shared libraries (so's) - each having object files of x (i.e x.o)? 
We will explore these and come back questions (Little Quiz) and re-answer them in the end.

Description

Consider that you are building and executable a.exe.
Now this executable consists of many objects and libraries - as shown in below diagram.




  1. libXYZ.so consists of 3 objects X.o, Y.o and Z.o 
  2. libVWX.so consists of 3 objects V.o, W.o and X.o 
  3. libWXY.so consists of 3 objects W.o, X.o and Y.o 
  4. and, the executable a.exe consists of  
    1. objects X.o, M.o and A.o and  
    2. shared libraries (linked using -L/-l option in make file) libXYZ.so, libVWX.so and libWXY.so. 
From the diagram,you can see that there are 4 copies of the x.o present in the final executable (represented by 4 copies of x.o object). Thus in the final executable, there are 4 copies of the static variable X::s in the system.
  • Does Building the executable like the above throw linker error during compilation (complaining of multiple definitions)? 
    • Surprisingly No!!!. From a detailed study I have done, the multiple definition is determined based on certain rule set - described below.
    • If the same symbol is found among the peers of objects and/or static library then it is flagged as error during compilation.For example
      • if and only if X.o and M.o contains same symbol, then it is flagged as an error - in case of bulding executable.
      • if and only if X.o and Y.o contains same symbol, then it is flagged as an error - in case of bulding a shared library.   
    •  During linking (last stage of execution building), the shared library symbols are not resolved. That means, linker will not go into the symbol definitions present in any of the linker - in case it is already present in the object file of the executable.
  • Now let us come to the most interesting fact, In such circumstances, which symbol is used? 
    • It depends on the OS. 
        SUSE 10 Linux: 
        Here, the first encountered symbol is always placed in the Global Symbol Table (GST).
        So during execution symbol resolution from any of the library, the GST is dipped into and     would always gets the first symbol - leading to always pointing to same copy of the variable. 

        HP-UX:
        Here, when the execution flows through a library, it would refer to its "global" variable in the scope of its library. This can be visualized as GST containing global variable with the resolution of the library name also.
 
       This is contrary to SUSE Linux.
         

In short, SUSE Linux seems to have a single copy of reference for the global variable across all the libraries, overlapping objects in the executable. However, HPUX seems to have global copy for each library and it would be referenced during the execution. 
  • Doesn't it lead to different kind of behavior in both SUSE and HPUX OS for the same machine?
    • Yes, Try it out!!!

  • How to avoid this kind of problem? 
    • The main problem in this example, is the use of global objects/static global variables in multiple shared libraries and also in the main object file. The general expectation is that the linker to complain the problem - but it does not.
So only other option is to change the libraries into static library and link to the executable, if no error is thrown, relink with shared libraries.
   
Conclusion 

Now let us come back to the little quiz (discussed in the beggining).
  • What is the scope of global variable (g_s) and static class variable (XYZ::s)?
    • (Previous answer)
      • Naturally, the answer would be global. 
      • For class static data member variable, the scope would be global with additional limitation of privilage (i.e all objects belonging to XYZ in the executable will have only one copy of s). 
    • (Now)
      • Global, with the scope of shared library (while building ".so") or executable. 
      • Symbol resolution is done at peer object (.o) or static library (.a).  
      • Recursive symbol resolution is not done among linked shared library to find out conflict resolution.
  • How many copies of the variable exists? 
    • (Previous answer) One 
    • (Now) One per the linkage scope (i.e shared library in this case, or executable for main object file) 
Regards,
Tech Unravel
Supreme Debugging

October 4, 2011

[C++] Additional parenthesis are always safe! Really?

Background


In general, a programmer would be advised to provide additional parenthesis in the code "when in doubts" with a presumption that it is safe. We will see on example here on how in certain scenarios it cannot be safe and programmer needs to be extra careful.

Consider this code (which does nothing but puts a map of class pointer index versus class pointer index)

Sample 1
1:  #include <iostream>  
2:  #include <unistd.h>  
3:  #include <map>  
4:  using namespace std;
.
.
51:  int main()  
52:  {  
53:       const unsigned int MAX_COUNT = 20000;  
54:       map <I*,J*> contextHolder;  
55:       char *p[MAX_COUNT];  
.
.
. 
62:       map <I*,J*>::iterator itr = contextHolder.begin();  
63:       map <I*,J*>::iterator itrEnd = contextHolder.end();  
64:       while(itr != itrEnd)  
65:       {  
66:            if(someCondition)  
67:            {  
68:                 itr->first->f();  
69:                 itr->second->f();  
70:                 I* pTempI = itr->first;  
71:                 J* pTempJ = itr->second;  
72:                 delete pTempI;  
73:                 delete pTempJ;  
74:                 contextHolder.erase(itr);  
75:            }  
76:            itr++;  
77:       } 
.
.
.
82:       return 0;  
83:  } 


Now, let us discuss the code which is highlighted.
contextHolder.erase(itr);

This would lead to well known problem - crash,
  • Line 74 deletes the contents pointing to itr - thereby invalidating the pointer.
  • Line 76 increments the invalidated pointer - thus causing the crash.
to solve this problem, developers are known to add the iteration increment during the erase operation itself. Like

Sample 2
64:       while(itr != itrEnd)  
65:       {  
66:            if(someCondition)  
67:            {  
68:                 itr->first->f();  
69:                 itr->second->f();  
70:                 I* pTempI = itr->first;  
71:                 J* pTempJ = itr->second;  
72:                 delete pTempI;  
73:                 delete pTempJ;  
74:                 contextHolder.erase(itr++);  
75:            }  
76:          else  
77:          {  
78:           //do something else.  
79:             itr++;  
80:          }  
81:       }  

The fix in line 74 (In sample 2 versus sample 1) solves the problem with an increment operator for the iterator (because increment is done before invalidating the iterator).  

Detailed Description


All is well till now. But assume that a amateur programmer, who is confused about the working of increment operation in a complex statement like that of Sample 2, line 74 adds an addition braces for the increment iterator.


Sample 3
64:       while(itr != itrEnd)  
65:       {  
66:            if(someCondition)  
67:            {  
68:                 itr->first->f();  
69:                 itr->second->f();  
70:                 I* pTempI = itr->first;  
71:                 J* pTempJ = itr->second;  
72:                 delete pTempI;  
73:                 delete pTempJ;  
74:          contextHolder.erase((itr++));  
75:            }  
76:          else  
77:          {  
78:           //do something else.  
79:             itr++;  
80:          }  
81:     

We see that the application crashes at line 74.

Analysis 


Now this seemingly innocent braces (highlighted in red) looks to do more harm than good. This is a classical coding problem - the iterator is extended (incremented) beyond the boundary (i.e itr.end() ) and then the map tries to erase the same element - "causing crash".

So adding this additional parenthesis lead to a disastrous bug. Moreover, hard to debug/difficult to reoccur.Any static check tool also will mostly will fail to unearth the problem.

Take Away's 


  • These kind of problems are very very difficult to catch at any cycle of SDLC phase. Even a reviewer (with immense experience) also will find difficult to catch these kind of issues.

  • It is best to share these kind of unique issues which occur in projects as technical sharing across the organisation - so that developers are sensitised (adj : having an allergy or peculiar or excessive susceptibility :)), to such potential problem in code.

  • It is best for the developer not to blindly follow "any rule", but to understand the concept behind the logic and how compiler interprets the code - in this case - the priority of execution in the complex execution statement.

Regards,
Tech Unravel
Supreme Debugging