C++ Core Guidelines: Bounds Safety

Today's post is about the second profile of the C++ Core Guidelines: Bounds Safety. The goal of the profile bounds safety is it that you operate inside the bounds of allocated memory.

 

industrsy

The profile names the two enemies for bounds safety: pointer arithmetic and array indexing. Additionally, when you use a pointer, it should only address a single object but not an array. To make the profile bounds safety complete, you should combine it with the rules to type safety and lifetime safety. Type safety was the topic of my two previous posts: C++ Core Guidelines: Type Safety and C++ Core Guidelines: Type Safety by Design. Lifetime safety will be the topic of my next post.

Bounds Safety

Bounds safety consists of four rules:

  • Bounds.1: Don’t use pointer arithmetic
  • Bounds.2: Only index into arrays using constant expressions
  • Bounds.3: No array-to-pointer decay
  • Bounds.4: Don’t use standard-library functions and types that are not bounds-checked

The four rules to bounds safety mention three rules of the C++ core guidelines. As in the last posts to the profiles, I will make my additions if necessary.

Bounds.1: Don’t use pointer arithmetic, Bounds.2: Only index into arrays using constant expressions, and Bounds.3: No array-to-pointer decay

The reason for the three rules boils down to the three do's: pass pointers to single objects (only), keep pointer arithmetic simple, and use std::span. The first do can also be formulated negatively: don't pass pointers to arrays. I assume you don't know std::span. std::span<T> represents a non-owning range of contiguous memory. This range can be an array, a pointer with a size, or a std::vector.

Let me cite the words of the guidelines: "Complicated pointer manipulation is a major source of errors.". Why should we care? Of course, our legacy code is full of functionality, such as this example:

void f(int* p, int count)
{
    if (count < 2) return;

    int* q = p + 1;    // BAD

    int n = *p++;      // BAD

    if (count < 6) return;

    p[4] = 1;          // BAD

    p[count - 1] = 2;  // BAD

    use(&p[0], 3);     // BAD
}

int myArray[100];     // (1)

f(myArray, 100),      // (2)

 

The main issue with this code is that the caller must provide the correct length of the C-array. If not, we get undefined behaviour.

Think about the last lines (1) and (2) for a few seconds. We start with an array and remove its type information by passing it to the function f. This process is called an array to pointer decay and is the reason for a lot of errors. Maybe we had a bad day, and we count the number of elements wrong or the size of C-array changed. Anyway, the result is the same: undefined behaviour. The same argumentation will also hold for a C-string.

What should we do? We should use a suitable data type. C++20 supports std::span. Have a look here:

void f(span<int> a) // BETTER: use span in the function declaration
{
    if (a.length() < 2) return;

    int n = a[0];      // OK

    span<int> q = a.subspan(1); // OK

    if (a.length() < 6) return;

    a[4] = 1;          // OK

    a[count - 1] = 2;  // OK

    use(a.data(), 3);  // OK
}

 

Fine! std::span checks at run-time its boundaries.

But I hear your complaints: We don't have C++20. No problem. It's quite easy to rewrite the functions f using the container std::array and the method std::array::at. Here we are:

// spanVersusArray.cpp

#include <algorithm>
#include <array>

void use(int*, int){}

void f(std::array<int, 100>& a){

    if (a.size() < 2) return;

    int n = a.at(0);      

    std::array<int, 99> q;
    std::copy(a.begin() + 1, a.end(), q.begin());      // (1)

    if (a.size() < 6) return;

    a.at(4) = 1;          

    a.at(a.size() - 1) = 2;

    use(a.data(), 3); 
}

int main(){

    std::array<int, 100> arr{};

    f(arr);
    
}

 

The std::array::at operator will check at runtime its bounds. If pos >= size(), you will get an std::out_of_range exception. If you look carefully at the spanVersusArray.cpp program, you will notice two issues. First, the expression (1) is more verbose than the std::span version and second, the size of the std::array is part of the signature of the function f. This is bad. I can only use f with the type std::array<int, 100>.  In this case, the checks of the array size inside the function are superfluous. 

To your rescue, C++ has templates; therefore, it's easy to overcome the type restrictions but staying type-safe.

 

// at.cpp

#include <algorithm>
#include <array>
#include <deque>
#include <string>
#include <vector>

template <typename T>
void use(T*, int){}

template <typename T>
void f(T& a){

    if (a.size() < 2) return;

    int n = a.at(0);      

    std::array<typename T::value_type , 99> q;                 // (5)
    std::copy(a.begin() + 1, a.end(), q.begin());     

    if (a.size() < 6) return;

    a.at(4) = 1;          

    a.at(a.size() - 1) = 2;

    use(a.data(), 3);                                          // (6)
}

int main(){

    std::array<int, 100> arr{};                                             
    f(arr);                                                    // (1)
    
    std::array<double, 20> arr2{};
    f(arr2);                                                   // (2)
    
    std::vector<double> vec{1, 2, 3, 4, 5, 6, 7, 8, 9};
    f(vec);                                                    // (3)
    
    std::string myString= "123456789";
    f(myString);                                               // (4)
    
    // std::deque<int> deq{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    // f(deq);                                                 
    
}

 

Now, the function f works for std::array's of different sizes and types (lines (1) and (2)) but also for an std::vector (3) or an std::string (4). This containers have in common that their data is stored in a contiguous memory block. This will no hold std::deque; therefore, the call a.data() in expression (6) fails. A std::deque is a kind of doubly-linked list of small memory blocks.

 deque

The expression T::value_type (5) helps me to get the underlying value type of each container. T is a so-called dependent type because T is a type parameter of the function template f. This is the reason, I have to give the compiler as a hint that T::value_type is a type: typename T::value_type.

Bounds.4: Don’t use standard-library functions and types that are not bounds-checked

I have already written a post C++ Core Guidelines: Avoid Bounds Errors. This post gives background information to this rule and provides do's. 

What's next?

The name of the third profile is Lifetime Safety Profile. This profile which is the topic of my next post boils down to one rule: Don’t dereference a possibly invalid pointer.

 

Thanks a lot to my Patreon Supporters: Paul Baxter,  Meeting C++, Matt Braun, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Marko, Ramesh Jangama, G Prvulovic, Reiner Eiteljörge, Benjamin Huth, Reinhold Dröge, Abernitzke, Richard Ohnemus , Frank Grimm, Sakib, Broeserl, and António Pina.  

 

Thanks in particular to:
 
crp4

 

   

Get your e-book at Leanpub:

The C++ Standard Library

 

Concurrency With Modern C++

 

Get Both as one Bundle

cover   ConcurrencyCoverFrame   bundle
With C++11, C++14, and C++17 we got a lot of new C++ libraries. In addition, the existing ones are greatly improved. The key idea of my book is to give you the necessary information to the current C++ libraries in about 200 pages.  

C++11 is the first C++ standard that deals with concurrency. The story goes on with C++17 and will continue with C++20.

I'll give you a detailed insight in the current and the upcoming concurrency in C++. This insight includes the theory and a lot of practice with more the 100 source files.

 

Get my books "The C++ Standard Library" (including C++17) and "Concurrency with Modern C++" in a bundle.

In sum, you get more than 600 pages full of modern C++ and more than 100 source files presenting concurrency in practice.

 

Get your interactive course

 

Modern C++ Concurrency in Practice

C++ Standard Library including C++14 & C++17

educative CLibrary

Based on my book "Concurrency with Modern C++" educative.io created an interactive course.

What's Inside?

  • 140 lessons
  • 110 code playgrounds => Runs in the browser
  • 78 code snippets
  • 55 illustrations

Based on my book "The C++ Standard Library" educative.io created an interactive course.

What's Inside?

  • 149 lessons
  • 111 code playgrounds => Runs in the browser
  • 164 code snippets
  • 25 illustrations

Add comment


My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 2421

All 2689954

Currently are 215 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments