Identifiers, constructors and calling conventions
Last updated
Last updated
After opening TrustInSoft CI Analyzer, take a look at the Overview panel on the left hand side, that confirms there are no undefined behaviors.
Since there are no undefined behaviors, the Interactive code panel in the middle displays by default the main
function:
The Interactive code panel displays pseudo-C code that preserves the exact semantics of the original C++ code that is analyzed. The differences with the C language and how the original C++ code was translated to this pseudo-C code are explained below.
The first thing to notice is that some names contain characters that are forbidden in C, like Matrix<2, 2>
or std::basic_ostream
, and may be prefixed by ...
. Names of entities in C++ code are actually mangled, the Interactive Code panel displays an unmangled version of them to be clearer.
When a name is too long, a shortened version of it is displayed in the Interactive code panel with ...
as prefix. Clicking on this prefix will display the full qualified name.
The first statement that is not a declaration is a call to the function __tis_globinit()
. This function represents the dynamic initialization phase of a C++ program (as stated in [basic.start.init]). It contains only calls to functions with names similar to X::Y::__tis_init_Z
, that are used to initialize the non-local variables X::Y::Z
.
Looking at the definition of the X::Y::__tis_init_Z
function will lead the Source Code panel to the body of the generated function initializing the variable X::Y::Z
The first statement of the main
function in the source code is:
and corresponds in the normalized code to the line:
Ctor
is the generic name that TrustInSoft CI assigns to C++ constructors and you can see that:
the constructor templated arguments are made explicit.
all initializers of the source code are passed as arguments.
there is an additional argument & matrix_a
.
All method calls are translated to regular C function calls, and as such they receive an additional argument which stands for the this
pointer. In case of constructors, this is the address of the object being initialized.
When looking at the constructor definition, you can see that it is calling the inherited constructor Matrix_base<2, 2>::Ctor<double, double, double, double>
with the same arguments, except that the this
pointer is shifted to its __parent__Matrix_base<2, 2, Matrix>
.
The corresponding part of the original code is:
Matrix<N, M>
inherits from Matrix_base<N, M, Matrix>
, and its constructor only transfers its arguments to the constructor of the parent class. On TrustInSoft CI, a class A
inheriting from a class B
is represented by a struct A
containing a field struct B __parent__B
. The initialization of the base B
of A
is translated into a call the function B::Ctor(&A.__parent__B)
. This structure layout can be observed in the example by looking at the definition of the type struct Matrix<2, 2>
.
The next statement of the main
function in the original code is:
and corresponds in the normalized code to the line:
The first thing to note is that the id
variable has an auto
type in the original source but is declared in the normalized code as:
TrustInSoft CI makes auto
types explicit, in the same way it instantiates template parameters.
Another difference is that in the normalized code the identity<2>
function takes an additional argument despite being a usual function and not a method. This is a consequence of the fact that, in C++, a non-POD (in the sense of [class]p10) value returned by a function may not live inside the function but inside its caller. To model this, a function returning a non-POD type receives an additional parameter which contains the address of the initialized object.
The next statement of the main
function in the original code is:
which, when clicked on, corresponds in the normalized code to:
In this case, one C++ statement is translated into a block containing multiple declarations and statements. The function is_invertible<2>
takes its argument by copy, as seen in its declaration:
and so its parameter has to be initialized with a new object. This is the purpose of the __tis_arg_*
family variables. In the current case, __tis_arg
is initialized by calling the copy constructor of Matrix<2, 2>
with the address of id
as source. Then, the address of the newly built __tis_arg
variable is given to the function is_invertible<2>
and the block around it delimits the lifetime of __tis_arg
. This is the semantics of passing arguments by copy, see [expr.call]p4 and in particular: “The initialization and destruction of each parameter occurs within the context of the calling function.”
This transformation does not happen when calling the copy constructor of Matrix<2, 2>
because its argument is a reference. References are converted to pointers, so taking a reference to an object is taking its address, and accessing a reference simply dereferences the pointer.
The next interesting statement is the one at line 37 of the original source:
which is translated in the normalized code as:
Again, this statement is decomposed into a block containing multiple statements but declaring this time a variable called __tis_tmp_62
. The __tis_tmp_*
family of variables correspond to temporary object (as defined in [class.temporary]) that can be introduced by complex expressions. This temporary object is declared inside the block as its lifetime is the one of the full expression, and has to be destroyed at its end if needed.