Sun's Forte compiler is less compliant with the ANSI/ISO C standard than the IBM VisualAge for C/C++ compiler. This difference can cause you problems when you're porting applications from Solaris to AIX. One particularly vexing problem is the different way the compilers handle temporary variables, and that's what we focus on in this article.
Most well-written C and C++ programs compile and run on AIX without change. A well-written program follows the use of good programming practices, including these:
- The program conforms to the ANSI/ISO C standard.
- Portability considerations are a high priority in the design, implementation, and maintenance phases of the software cycle
- Prototyped functions declarations are used throughout the code.
Uncovering compiler-generated differences is often frustrating and perplexing. Some programmers give up too easily and blame the compiler without stopping to consider whether bad coding practices might be the root cause. Let's face it -- it's hard to keep up with the standards, and most of the time when there's a problem, programmers just aren't aware they're writing non-standard C/C++ code. Relying upon compiler/runtime output doesn't mean the C/C++ program will behave correctly because the compiler might not be up to date with the latest standard.
We'll show a couple of examples that demonstrate the pitfalls with the misuse of temporary variables and how the compilers handle temporary variables differently. And we'll show how you can use standard coding practices to prevent these problems.
Misusing temporary variables, example 1
Suppose you want to port this block of code from Solaris to AIX.
#include <strings.h>
#include <iostream.h>
class Obj
{
private:
char* ptr;
public:
Obj();
~Obj();
operator const char*() const;
Obj& operator=(const Obj&);
Obj(const Obj&);
};
Obj::Obj()
{
cout << "Obj::Obj() this=" << (int)this << endl;
ptr = strdup("hello world");
}
Obj::Obj(const Obj& rhs)
{
cout << "Obj::Obj(const Obj&) this=" << (int)this << endl;
ptr = rhs.ptr ? strdup(rhs.ptr) : 0;
}
Obj& Obj::operator=(const Obj& rhs)
{
cout << "Obj::operator=(const Obj&) this=" << (int)this << endl;
ptr = rhs.ptr ? strdup(rhs.ptr) : 0;
return *this;
}
Obj::~Obj()
{
cout << "Obj::~Obj() this=" << (int)this << endl;
delete ptr;
}
Obj::operator const char*() const
{
cout << "Obj::operator const char*() this=" << (int)this << endl;
return ptr;
}
Obj func()
{
cout << "in func" << endl;
Obj obj;
cout << "obj created in func" << endl;
return obj;
}
main()
{
cout << "in main" << endl;
const char *val = func();
cout << "val=" << val << endl;
} |
To understand the problem, you need to understand what the compiler does with the line "const char val = func();". Func() returns an instance of Obj by value. To do this, the compiler creates a temporary variable to hold the return value. Another subtle thing that happens here is that the compiler generates a call to the Obj::operator const char * which takes a copy of the pointer, ptr, from the temporary object and assigns it to a val.
Output from the Solaris Forte compiler
Here's the output from the Solaris Forte compiler:
>/opt/bin/CC testptr.cpp >a.out in main in func Obj::Obj() this=-4261612 obj created in func Obj::Obj(const Obj&) this=-4261500 Obj::~Obj() this=-4261612 Obj::operator const char*() this=-4261500 val=hello world Obj::~Obj() this=-4261500 |
The output shows that the destructor of the temporary is called after the last line of user code in the program.
Output from the AIX VisualAge C++ compiler
Now compare the Solaris output with this output from the AIX VisualAgeC++ compiler:
>xlC testptr.cpp >a.out in main in func Obj::Obj() this=804398976 obj created in func Obj::operator const char*() this=804398976 Obj::~Obj() this=804398976 val= ² |
Here the destructor of the temporary was called at the end of the line in which it was created. The problem occurs when the temporary used to hold the return value called to the func() is deleted prior to the use of the pointer which was retrieved from using the operator const char*(). This is how it should behave, according to the standard. In section 12.2, Temporary Objects, clause 3, the ANSI/ISO C standard states: "... Temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created." (See http://www.ansi.org)
Okay, so how do you solve this problem?
There are several ways you can solve the problem. The simplest way is to move the creation of the temporary to the line where it's needed.
main()
{
cout << "in main" << endl;
cout << "val=" << (const char*)func() << endl;
} |
Here's another way to correct the bad coding behavior. Assign the result of the call to a real object and use on the real object to remove the reliance upon the temporary value. The result of the call to func() is assigned to the object val, which persists until the end of main. In fact using this form with the IBM compiler eliminates the need for a temporary. The call to func() constructs the Obj object right into val.
main()
{
cout << "in main" << endl;
Obj val = func();
cout << "val=" << val << endl;
} |
Misusing temporary variables, example 2
In this example, the application defines its own string class object:
>cat myString.h
#include <string>
#define MY_SL_STD(MY_NAME) ::std::MY_NAME
class MYString
{
public:
// common constructors
MYString() {}
MYString(const MY_SL_STD(string&) data) : data_(data) {}
MYString(const MYString& str) : data_(str.data_) {}
MYString(char c, size_t N) : data_(N, c) {}
MYString(const char* s) : data_(s) {}
MYString(const char* s, size_t N) : data_(s,N) {}
// constructor explicitly from char
MYString(char c) : data_(1,c) {}
~MYString() {}
const char* data() const { return data_.c_str(); }
// Type conversion:
operator const char*() const { return data_.c_str(); }
protected:
MY_SL_STD(string) data_;
};
>cat myString.C
#include <iostream.h>
#include "mystring.h"
class A
{
public:
A(const char * str_)
{
strcpy(str, str_);
}
MYString getStr()
{
return str;
}
void print()
{
cout<<"object A " << str <<endl;
}
private:
char str[2000];
};
void foo(const char* s)
{
cout<<"foo: "<<s<<endl;
}
int main()
{
A a("This is a test");
a.print();
const char * p = (const char*) a.getStr();
cout << "p=" << p << endl;
return (0);
} |
Output from the Solaris Forte compiler
Here's the output from the Solaris Forte compiler:
>CC myString.C >a.out object A This is a test p= This is a test |
Output from the AIX VisualAge compiler
Now compare to the output from the AIX VisualAge compiler:
>xlC myString.C >a.out object A This is a test p= e@ |
In this example, the char array in the object is converted to a MYString to pass it out of getStr. The method A::getStr would change to A::MYString object. Whenever the getStr is used, the program will generate incorrect results, thus indicating a problem.
When the call to a getStr() is made, the method takes the str buffer and constructs a MYString object. It is a MYString object that is returned, and the code calls a MYString method to convert to a const char *. This method returns a pointer to a temporary copy of the string, "This is a test." Once the pointer has been received and stored into the p variable, the destructor for the temporary object is called. This is where the memory referenced by p now no longer contains "This is a test." Since the character string is a temporary copy, the string doesn't exist anywhere except in the original location inside the object. Any use of the p will access garbage.
Because the creation of these temporary objects uses dynamic memory, you can't really depend upon the object sticking around any longer than the statement in which it is used.
Okay, so how do you solve this problem?
There are several ways to correct this problem:
int main()
{
A a(This is a test");
a.print();
cout<<"Problem= "<<(const char*) a.getStr()<<endl;
MYString testStr = a.getStr();
cout<<"(const char *) "<<(const char*) testStr<<endl;
cout<<"testStr " <<testStr<<endl;
cout<<"data() "<<testStr.data()<<endl;
foo((const char *) a.getStr());
return (0);
} |
For the testStr object, the temporary object destructor is invoked at the sequence point -- the semicolon at the end of the statement -- after the cout operations are performed. The use of testStr creates a new object that exists until the end of the function, so it can be used anywhere.
The output looks like this:
>xlC myString.C >a.out object A This is a test Problem= This is a test (const char *) This is a test testStr This is a test data() This is a test foo: This is a test |
Compiler generated differences are often the hardest and most puzzling of all problems to resolve. The immediate response is to blame the compiler/vendor without giving consideration to existing bad coding practices. In this paper, we have uncovered two similar practices that demonstrate how different compiler implementations handle temporary variables. We have also demonstrated how to correct these behavioral problems by using standard coding practices.
Nam Keung is a senior programmer who has worked in the area of AIX communication development, AIX multimedia, SOM/DSOM development and the java performance. His current assignment involves helping ISVs in application design, deploying applications, performance tuning, and education for the pSeries platform. He has been a programmer with IBM since 1987. You can contact Nam at namkeung@us.ibm.com.
Howard Nasgaard is an IBM Advisory Development Analyst in the Toronto Software Development Lab. He has worked in software development for 18 years, the last seven of which have been in C++ compiler development. He is currently architect for the C++ front end. You can contact Howard at nasgaard@ca.ibm.com.
Brad Cobb is a senior technical consultant for IBM's Solutions Development Group, where he assists independent software vendors to enable their applications on IBM eServer products. Brad has over fifteen years of experience porting applications to AIX with more than six years of experience working for IBM. He has worked with various application types that have allowed him to file five patent applications, publish several articles, and speak at developer conferences. Brad can be reached at: bcobb@us.ibm.com.
Comments (Undergoing maintenance)





