﻿# V835\. Passing cheap\-to\-copy argument by reference may lead to decreased performance\.

Анализатор обнаружил функцию, которая принимает параметр по ссылке на константный объект, когда эффективнее это делать по копии\.

Рассмотрим два примера для 64\-битных систем\.

В первом — функция принимает объекты типа 'const std::string\_view &':

```cpp
uint32_t foo_reference(const std::string_view &name) noexcept
{
  return static_cast<uint32_t>(8 + name.size()) + name[0];
}
```

Ассемблерный код:

```cpp
foo_reference(std::basic_string_view<char, std::char_traits<char> > const&):
        mov     eax, dword ptr [rdi]     // <= (1)
        mov     rcx, qword ptr [rdi + 8] // <= (2)
        movsx   ecx, byte ptr [rcx]
        add     eax, ecx
        add     eax, 8
        ret
```

В нем при каждом чтении данных из объекта типа 'const std::string\_view &' происходит разыменование\. Это инструкции 'mov eax, dword ptr \[rdi\]' \(1\) и 'mov rcx, qword ptr \[rdi \+ 8\] ' \(2\)\.

Во втором — функция принимает объекты типа 'std::string\_view':

```cpp
uint32_t foo_value(std::string_view name) noexcept
{
  return static_cast<uint32_t>(8 + name.size()) + name[0];
}
```

Ассемблерный код:

```cpp
foo_value(std::basic_string_view<char, std::char_traits<char> >):
        movsx   eax, byte ptr [rsi]
        add     eax, edi
        add     eax, 8
        ret
```

Компилятор сгенерировал меньше кода для второго примера\. Так происходит потому, что объект полностью помещается в регистры процессора и нет необходимости в адресации для доступа к нему\.

Давайте разберёмся, какие объекты выгоднее передавать по копии, а какие по ссылке\.

Обратимся к документу "[System V Application Binary Interface AMD64 Architecture Processor Supplement](https://raw.githubusercontent.com/wiki/hjl-tools/x86-psABI/x86-64-psABI-1.0.pdf)"\. В нём описаны соглашения о вызовах функций для Unix\-подобных систем\. Пункт 3\.2\.3 описывает передачу параметров\. Для каждого из них определяется свой класс\. Если параметр имеет класс MEMORY, то он будет передаваться через стек\. В противном случае параметр передаётся через регистры процессора, как в приведённом выше примере\. Согласно подпункту 5 \(C\), если размер объекта превышает 16 байт, то он имеет класс MEMORY\. Исключение составляют агрегатные типы размером до 64 байтов, первое поле которых имеют класс SSE, а все остальные SSEUP\. Это означает, что объекты, имеющие больший размер, будут размещаться на стеке вызова функции, и для доступа к ним также необходима адресация\.

Давайте рассмотрим ещё два примера для 64\-битных систем\.

В третьем — по копии принимается объект размером в 16 байт:

```cpp
struct SixteenBytes
{
    int64_t firstHalf;  // 8-byte
    int64_t secondHalf; // 8-byte
}; // 16-bytes

uint32_t foo_16(SixteenBytes obj) noexcept
{
  return obj.firstHalf + obj.secondHalf;
}
```

Ассемблерный код:

```cpp
foo_16(SixteenBytes):                    # @foo_16(SixteenBytes)
        lea     eax, [rsi + rdi]
        ret
```

Компилятор сгенерировал эффективный код, разместив структуру в двух 64\-битных регистрах\.

Во четвертом примере по копии принимается структура размером в 24 байта:

```cpp
struct MoreThanSixteenBytes
{
    int64_t firstHalf;        // 8-byte
    int64_t secondHalf;       // 8-byte
    int32_t yetAnotherStuff;  // 4-byte
}; // 24-bytes

uint32_t foo_more_than_16(MoreThanSixteenBytes obj) noexcept
{
  return obj.firstHalf + obj.secondHalf + obj.yetAnotherStuff;
}
```

Ассемблерный код:

```cpp
foo_more_than_16(MoreThanSixteenBytes):
        mov     eax, dword ptr [rsp + 16]
        add     eax, dword ptr [rsp + 8]
        add     eax, dword ptr [rsp + 24]
        ret
```

Согласно соглашению о вызовах, компилятор вынужден разместить структуру на стеке\. Это приводит к тому, что доступ к ней происходит косвенно, через адрес, который вычисляется с помощью регистра 'rsp'\. В таком случае будет выдано предупреждение [V813](https://pvs-studio.ru/ru/docs/warnings/v813/)\.

На Windows аналогичные правила вызовов функций\. Подробнее можно почитать в [документации](https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170#parameter-passing)\.

Диагностика отключена на 32\-битной платформе x86, так как на ней правила вызова функций отличаются в силу того, что не хватает регистров процессора для передачи аргументов\.

У диагностики возможны ложные срабатывания\. У ссылок на константные объекты могут быть необычные применения\. Например, функция, в которую передаётся некий объект по ссылке, может сохранить её в глобальное хранилище\. При этом сам объект, на который она ссылается, может изменяться\. 

Рассмотрим пример:

```cpp
struct RefStorage
{
  const int &m_value;

  RefStorage(const int &value)
    : m_value { value }
  {}

  RefStorage(const RefStorage &value)
    : m_value { value.m_value }
  {}
};

std::shared_ptr<RefStorage> rst;

void SafeReference(const int &ref)
{
  rst = std::make_shared<RefStorage>(ref);
}

void PrintReference()
{
  if (rst)
  {
    std::cout << rst->m_value << std::endl;
  }
}

void foo()
{
  int value = 10;
  SafeReference(value);

  PrintReference();

  ++value;

  PrintReference();
}
```

Функция 'foo' вызывает функцию 'SafeReference' и передаёт ей в качестве параметра переменную 'value' по ссылке\. Далее эта ссылка сохраняется в глобальное хранилище 'rst'\. При этом переменная 'value' может изменяться, так как она сама не константная\.

Приведённый код достаточно неестественный и плохо написан\. В реальных проектах могут быть и более сложные случаи\. Если программист знает, что делает, то диагностику можно подавить специальным комментарием '//\-V835'\.

Если в вашем проекте много таких мест, можно полностью отключить диагностику, добавив комментарий '//\-V::835' в предкомпилированный заголовок или '\.pvsconfig' файл\. Подробнее о подавлении ложных предупреждений можно прочитать в [документации](https://pvs-studio.ru/ru/docs/manual/0017/)\.