Main Content

Expensive-to-Read Objects in Bug Finder

R2026b

Polyspace® Bug Finder™ reports several defects when your code performs an expensive read operation that can instead be replaced by a more efficient read operation. When evaluating the efficiency of a read operation, Polyspace considers the costs of reading an object by copy and by dereferencing. An object can be more expensive to read by copy than by dereferencing, and vice versa. This topic clarifies when Bug Finder considers a copy operation to be expensive and when Bug Finder considers a dereferencing operation to be expensive.

Objects That Are Expensive to Read by Copy

During a read operation, Polyspace considers an object to be expensive to read by copy compared to dereferencing if either of these conditions are true:

  • The object is not a trivially copy-constructible type. A trivially copy-constructible type has a trivial copy constructor and a trivial destructor. To find out if the type of your object is trivially copy-constructible, use the C++ type trait std::is_trivially_copy_constructible.

  • The object is trivially copy-constructible but its size is greater than twice the size of a void* pointer:

    sizeof(object) > 2 × sizeof(void*)

Examples of objects that are expensive to read by copy include std::string objects, user-defined classes that are not trivially copy-constructible, most sequence containers such as std::vector, all associative containers such as std::map, and all unordered associative containers such as std::unordered_map.

Consider this code:

#include <vector>

extern void foo(std::vector<int> v);
extern void bar(const std::vector<int> &v);

void by_value(const std::vector<int> &v)
{
	foo(v);
}

void by_ref(const std::vector<int> &v)
{
	bar(v);
}

If you compile the code using GCC14.2 for an x86-64 target using the flag -Og, the function by_value() requires these instructions

by_value(std::vector<int, std::allocator<int> > const &):
	push    rbx
	sub     rsp, 32
	mov     rsi, rdi
	mov     rdi, rsp
	call    std::vector<int, std::allocator<int> >::vector(std::vector<int, std::allocator<int> > const &) [complete object constructor]
	mov     rdi, rsp
	call    foo(std::vector<int, std::allocator<int> >)
	mov     rdi, rsp
	call    std::_Vector_base<int, std::allocator<int> >::~_Vector_base() [base object destructor]
	add     rsp, 32
	pop     rbx
	ret
	mov     rbx, rax
	mov     rdi, rsp
	call    std::_Vector_base<int, std::allocator<int> >::~_Vector_base() [base object destructor]
	mov     rdi, rbx
	call    _Unwind_Resume

If you compile the code using GCC14.2 for an x86-64 target using the flag -Og, the function by_ref() requires these instructions

by_ref(std::vector<int, std::allocator<int> > const &):
	sub     rsp, 8
	call    bar(std::vector<int, std::allocator<int> > const &)
	add     rsp, 8
	ret

Both by_value() and by_ref() pass an std::vector object to a function. A copy of the vector is passed by to foo() while a reference to the vector is passed to bar(). Vector objects are not trivially copyable and their sizes are larger than 2 × sizeof(void*). In a typical toolchain, the passing the vector by value takes 18 instructions, including five call instructions. Passing the vector by reference takes five instructions with only one call instruction. Polyspace considers vector objects expensive-to-read by copy.

Objects That Are Expensive to Read by Dereference

During a read operation, Polyspace considers an object to be expensive to read by dereference compared to copying if both of these conditions are true:

  • The object has a trivially copy-constructible type.

  • The size of the object is less than or equal to 2 × sizeof(void*)

Typical examples of objects that are expensive to read by dereference include plain integral types of various sizes. For example, consider this code:

#include <cstddef>

size_t double_by_ref(size_t const &r)
{
	return 2 * r;
}
size_t double_by_value(size_t const r)
{
	return 2 * r;
}

If you compile the code using GCC14.2 for an x86-64 target using the flag -Og, the function double_by_ref() requires these instructions

double_by_ref(unsigned long const &):
	mov     rax, QWORD PTR [rdi]
	add     rax, rax
	ret

If you compile the code using GCC14.2 for an x86-64 target using the flag -Og, the function double_by_value() requires these instructions

double_by_value(unsigned long):
	lea     rax, [rdi + rdi]
	ret

The function double_by_ref() accepts a size_t object by reference while the function double_by_value() accepts a size_t object by value. Objects of type size_t are trivially-copyable and have size less than or equal to 2 × sizeof(void*). In a typical toolchain, the function double_by_ref() takes at least one more instruction than the function double_by_value(). Polyspace considers integral types expensive-to-read by dereference.

See Also

External Websites