You are currently browsing the archives for the C++ tag.

Function Parameters and Arguments on 32-bit ARM

January 7th, 2016

Niall Cooling

Director at Feabhas Limited
Co-Founder and Director of Feabhas since 1995.
Niall has been designing and programming embedded systems for over 30 years. He has worked in different sectors, including aerospace, telecomms, government and banking.
His current interest lie in IoT Security and Agile for Embedded Systems.

Latest posts by Niall Cooling (see all)

Function call basics

When teaching classes about embedded C  or embedded C++ programming, one of the topics we always address is “Where does the memory come from for function arguments?

Take the following simple C function:

void test_function(int a, int b, int c, int d);

when we invoke the function, where are the function arguments stored?

int main(void)
{
  //...
  test_function(1,2,3,4);
  //...
}

Unsurprisingly, the most common answer after “I don’t know” is “the stack“; and of course if you were compiling for x86 this would be true. This can be seen from the following x86 assembler for main setting up the call to test_function (Note: your milage will vary if compiled for a 64-bit processors):

  ...
  subl $16, %esp
  movl $4, 12(%esp)
  movl $3, 8(%esp)
  movl $2, 4(%esp)
  movl $1, (%esp)
  call _test_function
  ...

The stack is decremented by 16-bytes, then the four int’s are moved onto the stack prior to the call to test_function.

In addition to the function arguments being pushed, the call will also push the return address (i.e. the program counter of the next  instruction after the call) and, what in x86 terms, is often referred to as the saved frame pointer on to the stack. The frame pointer is used to reference local variables further stored on the stack.

This stack frame format is quite widely understood and historically been the target of malicious buffer overflows attacks by modifying the return address.

But, of course, we’re not here to discuss x86, it’s the ARM architecture we’re interested in.

The AAPCS

ARM is a RISC architecture; whereas the x86 is CISC. Since 2003 ARM have published a document detailing how separately compiled and linked code units work together. Over the years it has gone through a couple of name changes, but is now officially referred to as the “Procedure Call Standard for the ARM Architecture” or the AAPCS (I know, don’t ask!).

If we recompile main.c for ARM using the armcc compiler:

> armcc -S main.c

we get the following:

     ...
     MOV      r3,#4
     MOV      r2,#3
     MOV      r1,#2
     MOV      r0,#1
     BL       test_function
     ...

Here we can see that the four arguments have been placed in register r0-r3. This is followed by the “Relative branch with link” instruction. So how much stack has been used for this call? The short answer is none, as BL instruction moves the return address into the Link Register (lr/r14) rather than pushing it on to the stack, as per the x86 model.

Note: Around a function call there maybe other stack operations but that’s not the focus of this post

The Register Set

I’d imagine many readers are familiar with the ARM register set, but just to review;

  • There are 16 data/core registers r0-r15
  • Of these 16, three are special purpose registers
    • Register r13 acts as the stack pointer (SP)
    • Register r14 acts as the link register (LR)
    • Register r15 acts as the program counter (PC)

Basic Model

So the base function call model is that if there are four or fewer 32-bit parameters, r0 through r3 are used to pass the arguments and the call return address is stored in the link register.

If we add a fifth parameter, as in:

void test_function2(int a, int b, int c, int d, int e);
int main(void)
{
  //...
  test_function2(1,2,3,4,5);
  //...;
}

We get the following:

        ...
        MOV      r0,#5
        MOV      r3,#4
        MOV      r2,#3
        STR      r0,[sp,#0]
        MOV      r1,#2
        MOV      r0,#1
        BL       test_function2
        ...

Here, the fifth argument (5) is being stored on the stack prior to the call. 

Note however, in a larger code base you are likely to see at least one an extra stack “push” here (quite often r4) which is never accessed in the called function. This is because the stack alignment requirements defined by the AAPCS differ from functions called within the same translation unit to those called across translation units. The basic requirement of the stack is that:

SP % 4 == 0

However, the call is classes as a public interface, then the stack must adhere too:

SP % 8 == 0

Return values

Given the following code:

int test_function(int a, int b, int c, int d);
int val;
int main(void)
{
  //...
  val = test_function(1,2,3,4);
  //...
}

By analyzing the assembler we can see the return value is place in r0

        ...
        MOV      r3,#4
        MOV      r2,#3
        MOV      r1,#2
        MOV      r0,#1
        BL       test_function
        LDR      r1,|L0.40|  ; load address of extern val into r1
        STR      r0,[r1,#0]  ; store function return value in val
        ...

C99 long long Arguments

The AAPCS defines the size and alignment of the C base types. The C99 long long is 8 bytes in size and alignment. So how does this change our model?

Given:

long long test_ll(long long a, long long b);

long long ll_val;
extern long long ll_p1;
extern long long ll_p2;

int main(void)
{
  //...
  ll_val = test_ll(ll_p1, ll_p2);
  //...
}

We get:

   ...
   LDR      r0,|L0.40|
   LDR      r1,|L0.44|
   LDRD     r2,r3,[r0,#0]
   LDRD     r0,r1,[r1,#0]
   BL       test_ll
   LDR      r2,|L0.48|
   STRD     r0,r1,[r2,#0]
   ...
|L0.40|
   DCD      ll_p2
|L0.44|
   DCD      ll_p1

This code demonstrates that an 64-bit long long uses two registers (r0-r1 for the first parameter and r2-r3 for the second). In addition, the 64-bit return value has come back in r0-r1.

Doubles

As with the long long, a double type (based on the IEEE 754 standard) is also 8-bytes in size and alignment on ARM. However the code generated will depend on the actual core. For example, given the code:

double test_dbl(double a, double b);

double dval;
extern double dbl_p1;
extern double dbl_p2;

int main(void)
{
  //...
  dval = test_dbl(dbl_p1, dbl_p2);
  //...
}

When compiled for a Cortex-M3 (armcc –cpu=Cortex-M3 –c99 -S main.c) the output is almost identical to the long long example:

        ...
        LDR      r0,|L0.28|
        LDR      r1,|L0.32|
        LDRD     r2,r3,[r0,#0]
        LDRD     r0,r1,[r1,#0]
        BL       test_dbl
        LDR      r2,|L0.36|
        STRD     r0,r1,[r2,#0]
        ...
|L0.28|
        DCD      dbl_p2
|L0.32|
        DCD      dbl_p1

However, if we recompile this for a Cortex-A9 (armcc –cpu=Cortex-A9 –c99 -S main.c), note we get quite different generated instructions:

        ...
        LDR r0,|L0.40|
        VLDR d1,[r0,#0]
        LDR r0,|L0.44|
        VLDR d0,[r0,#0]
        BL test_dbl
        LDR r0,|L0.48|
        VSTR d0,[r0,#0]
        ...
|L0.40|
        DCD dbl_p2
|L0.44|
        DCD dbl_p1

The VLDR and VSTR instructions are generated as the Cortex-A9 has Vector Floating Point (VFP) technology.

Mixing 32-bit and 64-bit parameters

Assuming we change our function to accept a mixture of 32-bit and 64-bit parameters, e.g.

void test_iil(int a, int b, long long c);
extern long long ll_p1;

int main(void)
{
   //...
   test_iil(1, 2, ll_p1);
   //...
}

As expected we get; a in r0, b in r1 and ll_p1 in r2-r3.

       ...
       LDR r0,|L0.32|
       MOV r1,#2
       LDRD r2,r3,[r0,#0]
       MOV r0,#1
       BL test_iil
       ...
|L0.32|
       DCD ll_p1

However, if we subtly change the order to:

void test_iil(int a, long long c, int b);
extern long long ll_p1;
int main(void)
{
   //...
   test_ili(1,ll_p1,2);
   //...
}

We get a different result; a is in r0, c is in r2-r3, but now b is stored on the stack (remember this may also include extra stack alignment operations).

      ...
      MOV r0,#2
      STR r0,[sp,#0] ; store parameter b on the stack
      LDR r0,|L0.36|
      LDRD r2,r3,[r0,#0]
      MOV r0,#1
      BL test_ili
      ...
|L0.36|
      DCD ll_p1

So why doesn’t parameter ‘c’ use r1-r2? because the AAPCS states:

“A double-word sized type is passed in two consecutive registers (e.g., r0 and r1, or r2 and r3). The content of the registers is as if the value had been loaded from memory representation with a single LDM instruction”

As the complier is not allowed to rearrange parameter ordering, then unfortunately the parameter ‘b’ has to come in order after ‘c’ and therefore cannot use the unused register r1 and ends up on the stack.

C++

For all you C++ programmers out there, it is important to realize that for class member functions the implicit ‘this’ argument is passed as a 32-bit value in r0. So, hopefully, you can see the implications if targeting ARM of:

class Ex
{
public:
    void mf(long long d, int i);
};

vs.

class Ex
{
public:
    void mf(int i, long long d);
};

Summary

Even though keeping arguments in registers may be seen as “marginal gains“, for large code bases I have seen, first-hand, significant performance and power improvements simply by rearranging the parameter ordering.

And finally…

I’ll leave you with one more bit of code to puzzle over. An often quoted guideline when programming in C is not to pass struct’s by value, but rather to pass by pointer.

So given the following code:

typedef struct
{
   int a;
   int b;
   int c;
   int d;
} Example;

void pass_by_copy(Example p);
void pass_by_ptr(const Example* const p);

Example ex = {1,2,3,4};

int main(void)
{
   //...
   pass_by_copy(ex);
   pass_by_ptr(&ex);
   //...
}

Can you guess/predict the difference in performance and memory implications of each option?

Feabhas embedded programming training courses

This post originally appear on the ARM Connected Community site

Seeing stars. And dots. And arrows.

December 10th, 2015

Glennan Carnie

Technical Consultant at Feabhas Ltd
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

Latest posts by Glennan Carnie (see all)

This time I want to look at a seemingly trivial concept in C++ programming: accessing class members, either directly or via a pointer.  More than anything it’s an excuse to talk about two of C++’s more obscure operators – .* and ->*

Read more »

Bitesize Modern C++ : Smart pointers

October 22nd, 2015

Glennan Carnie

Technical Consultant at Feabhas Ltd
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

Latest posts by Glennan Carnie (see all)

The dynamic creation and destruction of objects was always one of the bugbears of C. It required the programmer to (manually) control the allocation of memory for the object, handle the object’s initialisation then ensure that the object was safely cleaned-up after use and its memory returned to the heap. Because many C programmers weren’t educated in the potential problems (or were just plain lazy or delinquent in their programming) C got a reputation in some quarters for being an unsafe, memory-leaking language.

Things didn’t significantly improve in C++. We replaced malloc and free with new and delete; but the memory management issue remained.

image

I concede – the code above is trivial and stupid but I suspect if I looked around I could find similar (or even worse!) examples in actual production code.

Languages such as Java and C# solved this problem by taking memory management out of the hands of the programmer and using a garbage collector mechanism to ensure memory is cleaned up when not in use.

In Modern C++ they have chosen not to go down this route but instead make use of C++’s Resource Acquisition Is Initialisation (RAII) mechanism to encapsulate dynamic object creation / destruction within smart pointers.

A smart pointer is basically a class that has the API of a ‘raw’ pointer. In Modern C++ we have four classes for dynamic object management:

std::auto_ptr : Single-owner managed pointer, from C++98; now deprecated

std::shared_ptr : A reference-counted pointer, introduced in C++98 TR1

std::unique_ptr : Single-owner managed pointer which replaces (the now deprecated) auto_ptr

std::weak_ptr : Works with shared_ptr in situations where circular references could be a problem

 

Avoid using std::auto_ptr

std::auto_ptr was introduced in C++98 as a single-owner resource-managed smart pointer. That is, only one auto_ptr can ever be pointing at the resource.

auto_ptr objects have the peculiarity of taking ownership of the pointers assigned (or copied) to them: An auto_ptr object that has ownership over one element is in charge of destroying the element it points to and to deallocate the memory allocated to it when itself is destroyed. The destructor does this by calling delete automatically.

image

When an assignment operation takes place between two auto_ptr objects, ownership is transferred, which means that the object losing ownership is set to no longer point to the element (it is set to nullptr).   This also happens if you copy from one auto_ptr to another – either explicitly, or by passing an auto_ptr to a function by value.

This could lead to unexpected null pointer dereferences – an unacceptable consequence for most (if not all) systems. Therefore, we recommend avoiding the use of auto_ptr. It has now been deprecated in C++11 (and replaced with the much more consistent std::unique_ptr)

 

Use std::unique_ptr for single ownership

std::unique_ptr allows single ownership of a resource. A std::unique_ptr is an RAII wrapper around a ‘raw’ pointer, therefore occupies no more memory (and is generally as fast) as using a raw pointer. Unless you need more complex semantics, unique_ptr is your go-to smart pointer.

unique_ptr does not allow copying (by definition); but it does support move semantics, so you can explicitly transfer ownership of the resource to another unique_ptr.

 

The utility function make_unique<T>() hides away the memory allocation and is the preferred mechanism for dynamically creating objects. make_unique<T>() is not officially supported in C++11; but it is part of C++14 and is supported by many C++11-compliant compilers. (A quick search will turn up an implementation if your compiler doesn’t currently support it)

image

For sharing a resource, use std::shared_ptr

std::shared_ptr is a reference-counted smart pointer.

Creating a new dynamic object also creates a new associated management structure that holds (amongst other things) a reference count of the number of shared_ptrs currently ‘pointing’ at the object.

Each time a shared_ptr is copied the reference count is incremented. Each time one of the pointers goes out of scope the reference count on the resource is decremented. When the reference count is zero (that is, the last shared_ptr referencing the resource goes out of scope) the resource is deleted.

std::shared_ptrs have a higher overhead (in memory and code) than std::unique_ptr but they come with more sophisticated behaviours (like the ability to be copied at relatively low cost).

image

Once again, the standard library provides a utility function make_shared<T>() for creating shared dynamic objects; and, once again, this is the preferred mechanism.

 

Use std::weak_ptr for tracking std::shared_ptrs

A std::weak_ptr is related to a std::shared_ptr. Think of a weak_ptr as a ‘placeholder’ for a shared_ptr. std::weak_ptrs are useful if you want to track the existence of a resource without the overhead of a shared_ptr; or you need to break cyclic dependencies between shared_ptrs (A topic that is outside the scope of this article; but have a look here if you’re interested)

When you create a weak_ptr it must be constructed with an extant shared_ptr. It then becomes a ‘placeholder’ for that shared_ptr. You can store weak_ptrs, copy and move them, but doing so has no effect the reference count on the resource.

image

Note you cannot directly use a weak_ptr. You must convert it back to a shared_ptr first. weak_ptrs have a method, lock(), that creates (in effect) a copy of the original shared_ptr, which can then be accessed.

image

Since weak_ptrs can have a different lifetime to their associated shared_ptr there is a chance the original shared_ptr could go out of scope (and conceptually delete its resource) before the weak_ptr is destroyed.  (Strictly speaking, the resource is deleted when the last referencing shared_ptr and/or weak_ptr have gone out of scope)

A weak_ptr can therefore be invalid – that is, referencing a resource that is no longer viable. You should use the expired() method on the weak_ptr to see if it is still valid, before attempting to access it (alternatively, calling lock() on an expired weak_ptr will return nullptr).

 

That’s all for now.

We’ve got to the end of the Bitesize series for Modern C++.  You should now be in a much stronger position to explore the new features of C++ in more detail.

If you missed an article, or just want the complete set in a single document, you can download the full set of articles as a PDF, here.

To learn more about Feabhas’ Modern C++ training courses, click here.

Bitesize Modern C++ : std::array

October 8th, 2015

Glennan Carnie

Technical Consultant at Feabhas Ltd
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

Latest posts by Glennan Carnie (see all)

C++98 inherited C’s only built-in container, the array. Arrays of non-class types behave in exactly the same way as they do in C. For class types, when an array is constructed the default constructor is called on each element in the arrayimage

Explicitly initialising objects in an array is one of the few times you can explicitly invoke a class’s constructor.

image

For track[], the non-default constructor is called for first three elements, followed by default (no parameter) constructor for the last two elements; hence they are 0.0.

(Note the performance implications of this – five constructor calls will be made whether you explicitly initialise the objects or not.)

Arrays are referred to as ‘degenerate’ containers; or, put more antagonistically: they are a lie.

Arrays are basically a contiguous sequence of memory, pointers, and some syntactic sugar. This can lead to some disturbing self-delusion on the part of the programmer.

image

Despite the fact that the declaration of process() appears to specify an array of five Position objects, it is in fact a simple Position* that is passed. This explains why the array_sizeof macro fails (since the size of a Position is greater than the size of a pointer!). It also explains why we can increment the array name (which should be a constant) – as it is in main())

In C++11, use of ‘raw’ arrays is undesirable; and there are more effective alternatives.

std::array is fixed-size contiguous container. The class is a template with two parameters – the type held in the container; and the size.

image

std::array does not perform any dynamic memory allocation. Basically, it’s a thin wrapper around C-style arrays. Memory is allocated – as with built-in arrays – on the stack or in static memory. Because of this, and unlike std::vector, std::arrays cannot be resized.

If C-style notation is used there is no bounds-checking on the std::array; however, if the at() function is used an exception (std::out_of_range) will be thrown if an attempt is made to access outside the range of the array.

std::arrays also have the advantage that they support all the facilities required by the STL algorithms so they can be used wherever a vector or list (etc.) could be used; without the overhead of dynamic memory management.

image

Finally, because container types are classes (not syntactic sugar) they can be passed around the system like ‘proper’ objects.

image

 

More information

Can’t wait? Download the full set of articles as a PDF, here.

To learn more about Feabhas’ Modern C++ training courses, click here.

Bitesize Modern C++ : noexcept

September 24th, 2015

Glennan Carnie

Technical Consultant at Feabhas Ltd
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

Latest posts by Glennan Carnie (see all)

We have some basic problems when trying to define error management in C:

  • There is no “standard” way of reporting errors. Each company / project / programmer has a different approach
  • Given the basic approaches, you cannot guarantee the error will be acted upon.
  • There are difficulties with error propagation; particularly with nested calls.

The C++ exception mechanism gives us a facility to deal with run-time errors or fault conditions that make further execution of a program meaningless.

In C++98 it is possible to specify in a function declaration which exceptions a function may throw.

image

The above function declarations state:

  • get_value() can throw any exception. This is the default.
  • display() will not throw any exceptions.
  • set_value() can throw exceptions of only of type char* and Sensor_Failed; it cannot throw exceptions of any other type.

This looks wonderful, but compilers (can) only partially check exception specifications at compile-time for compliancy.

image

If process() throws an exception of any type other than std::out_of_range this will cause the exception handling mechanism – at run-time – to call the function std::unexpected() which, by default, calls std::terminate() (although its behaviour can – and probably should – be replaced).

Because of the limitations of compile-time checking, for C++11 the exception specification was simplified to two cases:

  • A function may propagate any exception; as before, the default case
  • A function may not throw any exceptions.

Marking a function as throwing no exceptions is done with the exception specifier, noexcept.

(If you read the noexcept documentation you’ll see it can take a boolean constant-expression parameter. This parameter allows (for example) template code to conditionally restrict the exception signature of a function based on the properties of its parameter type. noexcept on its own is equivalent to noexcept(true). The use of this mechanism is beyond the scope of this article.)

image

On the face of it, the following function specifications look semantically identical – both state that the function will not throw any exceptions:

image

The difference is in the run-time behaviour and its consequences for optimisation.

With the throw() specification, if the function (or one of its subordinates) throws an exception, the exception handling mechanism must unwind the stack looking for a ‘propagation barrier’ – a (set of) catch clauses. Here, the exception specification is checked and, if the exception being thrown doesn’t match the provided specification, std::unexpected() is called.

However, std::unexpected() can itself throw an exception. If the exception thrown by std::enexpected() is valid for the current exception specification, exception propagation and stack unwinding continues as before.

This means that there is little opportunity for optimisation by the compiler for code using a throw() specification; in fact, the compiler may even introduce pessimisations to the code:

  • The stack must be maintained in an unwindable state.
  • Destructor order must be maintained to ensure objects going out of scope as a result of the exception are destroyed in the opposite order to their construction.
  • The compiler may introduce new propagation barriers to the code, introducing new exception table entries, thus making the exception handling code bigger.
  • Inlining may be disabled for the function.

In contrast, in the case of a noexcept function specification std::terminate() is called immediately, rather than std::unexpected(). Because of this, the compiler has the opportunity to not have to unwind the stack during an exception, allowing it a much wider range of optimisations.

In general, then, if you know your function will never throw an exception, prefer to specify it as noexcept, rather than throw().

 

More information

Can’t wait? Download the full set of articles as a PDF, here.

To learn more about Feabhas’ Modern C++ training courses, click here.

Bitesize Modern C++ : Override and Final

September 10th, 2015

Glennan Carnie

Technical Consultant at Feabhas Ltd
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

Latest posts by Glennan Carnie (see all)

Override specifier

In C++98 using polymorphic types can sometimes lead to head-scratching results:image

On the face of it this code looks sound; indeed it will compile with no errors or warnings. However, when it runs the Base version of op() will be executed!

The reason? Derived’s version of op() is not actually an override of Base::op since int and long are considered different types (it’s actually a conversion between an int and a long, not a promotion)

The compiler is more than happy to let you overload functions in the Derived class interface; but in order to call the overload you would need to (dynamic) cast the Base class object in usePolymorphicObject().

In C++11 the override specifier is a compile-time check to ensure you are, in fact, overriding a base class method, rather than simply overloading it.

image

Final specifier

In some cases you want to make a virtual function a ‘leaf’ function – that is, no derived class can override the method. The final specifier provides a compile-time check for this:

image

More information

Can’t wait? Download the full set of articles as a PDF, here.

To learn more about Feabhas’ Modern C++ training courses, click here.

Bitesize Modern C++ : Range-for loops

August 27th, 2015

Glennan Carnie

Technical Consultant at Feabhas Ltd
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

Latest posts by Glennan Carnie (see all)

If you’re using container classes in your C++ code (and you probably should be, even if it’s just std::array) then one of the things you’re going to want to do (a lot) is iterate through the container accessing each member in turn.

Without resorting to STL algorithms we could use a for-loop to iterate through the container.

image

If the above is baffling to you there are plenty of useful little tutorials on the STL on the Internet (For example, this one)

We could simplify the iterator declaration in C++11 using auto:

image

(See the article on auto type-deduction for details of how it works)

However, there’s a nicer syntactic sugar to improve our code: the range-for loop:

image

The semantics of the range-for are: For every element in the container, v, create a reference to each element in turn, item.

The above code is semantically equivalent to the following:

image

Look familiar?

Not only does this save you some typing but, because it’s the compiler that’s generating the code it has a lot more potential for optimisation (for example, the compiler knows that the end() iterator is not invalidated in the body of the range-for statement, therefore it can be read once before the loop; or the compiler may choose to unroll the loop; etc.)

In case you were wondering, std::begin() and std::end() are free functions that return an iterator to the first element in the supplied container and an iterator to one-past-the-end, respectively. For most STL containers they simply call cont.begin() and cont.end(); but the functions are overloaded to handle built-in arrays and other container-like objects (see below)

Range-for loops are not limited to STL containers. They can also work with built-in arrays:

image

And also std::initializer_lists

image

More information

Can’t wait? Download the full set of articles as a PDF, here.

To learn more about Feabhas’ Modern C++ training courses, click here.

Bitesize Modern C++: std::initializer_list

August 13th, 2015

Glennan Carnie

Technical Consultant at Feabhas Ltd
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

Latest posts by Glennan Carnie (see all)

An aggregate type in C++ is a type that can be initialised with a brace-enclosed list of initialisers. C++ contains three basic aggregate types, inherited from C:

  • arrays
  • structures
  • unions

Since one of the design goals of C++ was to emulate the behaviour of built-in types it seems reasonable that you should be able to initialise user-defined aggregate types (containers, etc.) in the same way.

image

A std::initializer_list is a template class that allows a user-defined type to become an aggregate type.

When initialiser list syntax is used the compiler generates a std::initializer_list object containing the initialisation objects. A std::initializer_list is a simple container class that may be queried for its size; or iterated through.

image

If the class contains a constructor that takes a std::initializer_list as a parameter, this constructor is invoked and the std::initializer_list object passed.

image

Note, there is some syntactic sugar at work here – the lack of brackets ([]) in the declaration of aggr forces the compiler to construct the std::initializer_list (then call aggr‘s constructor) rather than creating an array of three Aggregate objects.

This is also a good place to insert some words of caution: Adding std::initializer_list constructor overloads may lead to unexpected results:

image

If a class has constructors overloaded for T and std::initializer_list<T> the compiler will always prefer the std::initializer_list overload. However, if you’ve provided a default constructor the compiler will always prefer that to calling the std::initializer_list overload with an empty initialiser list.

Initialiser lists can begin to look like so much magic and ‘handwavium’, so a brief look at an implementation of std::initializer_list is useful to dispel the mysticism:

image

When the compiler creates an std::initializer_list the elements of the list are constructed on the stack (or in static memory, depending on the scope of the initializer_list).

image

The compiler then creates the initializer_list object that holds the address of the first element and one-past-the-end of the last element. Note that the initializer_list is very small (two pointers) so can be passed by copy without a huge overhead; it does not pass the initialiser objects themselves. Once the initializer_list has been copied the receiver can access the elements and do whatever needs to be done with them.

Since C++11 all the STL containers support std::initializer_list construction; so now lists and vectors can be initialised in the same way as built-in arrays.

image

 

More information

Can’t wait? Download the full set of articles as a PDF, here.

To learn more about Feabhas’ Modern C++ training courses, click here.

Bitesize Modern C++: Uniform initialization

July 30th, 2015

Glennan Carnie

Technical Consultant at Feabhas Ltd
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

Latest posts by Glennan Carnie (see all)

C++98 has a frustratingly large number of ways of initialising an object.

image

(Note: not all these initialisations may be valid at the same time, or at all. We’re interested in the syntax here, not the semantics of the class X)

One of the design goals in C++11 was uniform initialisation syntax. That is, wherever possible, to use a consistent syntax for initialising any object. The aim was to make the language more consistent, therefore easier to learn (for beginners), and leading to less time spent debugging.

To that end they added brace-initialisation to the language.

As the name would suggest, brace-initialisation uses braces ({}) to enclose initialiser values. So extending the above examples:

image

There are a couple of highlights from the above code:

Integer i is default-initialised (with the value 0). This is equivalent to C++03’s (much more confusing):

image

x1 is explicitly default-constructed. This alleviates the ‘classic’ mistake made by almost all C++ programmers at some point in their career:

image

By extension, this also alleviates C++’s Most Vexing Parse as well. For those not familiar with it, here it is:

image

Most programmers read this as "create an object, adt, and initialise it with a temporary object, ADT()". Your compiler, however, following the C++ parsing rules, reads it as "adt is a function declaration for a function returning an ADT object, and taking a (pointer to) a function with zero parameters, returning an ADT object."

With brace-initialisation, this problem goes away:

image

The compiler cannot parse the above except as "create an object, adt, and initialise it with a temporary object, ADT{}"

The uniform initialisation syntax goal means that brace-initialisation can be used anywhere an object must be initialised. This includes the member initialisation list:

image

In C++98 programmers had the capability to initialise static member variables as part of the class declaration. C++11 extends this to allow default-initialisation of non-static class members. The code:

image

Can be re-written as:

image

The member initialiser code ensures that the member variable will always have a default value, even if it is not explicitly initialised in a member initialiser list. For the example above we have told the compiler to provide the default constructor; which does nothing.

When we create object adt1 the (compiler-supplied) default constructor is called. The member initialisers in the class-definition ensure that the members of adt1 are initialised.

Having the initialisers visible at point-of-instantiation gives the compiler the opportunity to optimise (away) constructor calls and create the object in-situ.

 

More information

Can’t wait? Download the full set of articles as a PDF, here.

To learn more about Feabhas’ Modern C++ training courses, click here.

Bitesize Modern C++: using aliases

July 16th, 2015

Glennan Carnie

Technical Consultant at Feabhas Ltd
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

Latest posts by Glennan Carnie (see all)

In a C++ program it is common to create type aliases using typedef. A type alias is not a new type, simply a new name for an existing declaration. Used carefully, typedef can improve the readability and maintainability of code – particularly when dealing with complex declarations.

image

In C++11 typedef can be replaced with a using-alias. This performs the same function as a typedef; although the syntax is (arguably) more readable. A using-alias can be used wherever a typedef could be used.

image

Using-aliases have the advantage that they can also be templates, allowing a partial substitution of template parameters.

image

More information

Can’t wait? Download the full set of articles as a PDF, here.

To learn more about Feabhas’ Modern C++ training courses, click here.

%d bloggers like this: