﻿# V510\. The 'Foo' function receives class\-type variable as Nth actual argument\. This is unexpected behavior\.

Вариативная функция \(функция, последним формальным параметром которой является эллипсис\) принимает в качестве фактического аргумента, являющегося частью эллипсиса, объект классового типа, что может свидетельствовать о логической ошибке\. В качестве фактического параметра для эллипсиса могут выступать только POD\-типы\.

POD – это аббревиатура от "Plain Old Data", что можно перевести как "Простые данные в стиле C"\. Начиная с C\+\+11, к POD\-типам относятся:

1. Скалярные типы: арифметические типы \(целые и вещественные\), указатели, указатели на нестатические поля или функции класса, перечисления \('enum'\) или 'std::nullptr\_t' \(могут быть 'const' / 'volatile' квалифицированными\);
1. Классовый тип \('class', 'struct' или 'union'\), который удовлетворяет нижеприведенным требованиям: 
    * Конструкторы копирования/перемещения тривиальны \(сгенерированы компилятором или помечены как '\= default'\);
    * Операторы копирования/перемещения тривиальны \(сгенерированы компилятором или помечены как '\= default'\);
    * Имеет неудаленный тривиальный деструктор;
    * Конструктор по умолчанию тривиален \(сгенерирован компилятором или помечен как '\= default'\);
    * Все нестатические поля имеют одинаковый доступ \('private', 'protected' или 'public'\);
    * Не имеет виртуальных функций или виртуальных базовых классов;
    * Не имеет нестатических полей ссылочного типа;
    * Все нестатические поля и базовые классы имеют тип со стандартным размещением в памяти;
    * Не имеют базовых классов с нестатическими полями или не имеют полей в самом производном классе и не более одного базового класса с нестатическими полями;
    * Не имеют базовых классов того же типа, что имеет первое нестатическое поле\.

Если эллипсису функции в качестве параметра передается объект не POD\-типа, это практически всегда свидетельствует о наличии ошибки в программе\. Согласно стандарту [C\+\+11](https://timsong-cpp.github.io/cppwp/n3337/expr.call#7):

> Passing a potentially\-evaluated argument of class type having a non\-trivial copy constructor, a non\-trivial move constructor, or a non\-trivial destructor, with no corresponding parameter, is conditionally\-supported with implementation\-defined semantics\.

Пример кода с ошибкой:

```cpp
void bar(size_t count, ...);

void foo()
{
  std::string s1 = ....;
  std::string s2 = ....;
  std::string s3 = ....;

  bar(3, s1, s2, s3);
}
```

Начиная с C\+\+11, для исправления ошибки можно воспользоваться вариативными шаблонами, благодаря которым информация о типах переданных аргументов будет сохранена:

```cpp
template <typename T, typename ...Ts>
void bar(T &&arg, Ts &&...args);

void foo()
{
  std::string s1 = ....;
  std::string s2 = ....;
  std::string s3 = ....;

  bar(s1, s2, s3);
}
```

Анализатор не будет выдавать предупреждение, если передача объекта не POD\-типа происходит в невычисляемом контексте \(например, внутри операторов 'sizeof' / 'alignof'\):

```cpp
int bar(size_t count, ...);

void foo()
{
  auto res = sizeof(bar(2, std::string {}, std::string {}));
}
```

На практике диагностическое правило V510 помогает выявлять ошибки при передаче аргументов в функции форматного ввода/вывода из C:

```cpp
void foo(const std::wstring &ws)
{
  wchar_t buf[100];
  swprintf(buf, L"%s", ws);
}
```

Вместо указателя на строку в стек попадает содержимое объекта\. Такой код приведет к формированию в буфере "абракадабры" или к аварийному завершению программы\. 

Корректный вариант кода должен выглядеть так:

```cpp
wchar_t buf[100];
std::wstring ws(L"12345");
swprintf(buf, L"%s", ws.c_str());
```

Вместо printf\-подобных функций в C\+\+ рекомендуется использовать более безопасные аналоги\. Например, 'boost::format', 'fmt::format', 'std::format' \(C\+\+20\) и т\.п\.

**Примечание\.** Диагностическое правило V510 также рассматривает объекты POD\-типов при их передаче в функции форматного ввода/вывода\. Несмотря на то, что такая передача безопасна, дальнейшая работа функции с такими аргументами может привести к непредвиденным результатам\.

Если ложные срабатывания диагностического правила доставляют неудобства, их можно подавить на конкретной функции, вставив в код [комментарий](https://pvs-studio.ru/ru/docs/manual/0017/#ID9738B8CBFD) специального вида:

```cpp
//-V:MyPrintf:510
```

## Особенность при использовании класса CString из библиотеки MFC

Ошибку, аналогичную приведенной выше, мы должны наблюдать и в следующем коде:

```cpp
void foo()
{
  CString s;
  CString arg(L"OK");
  s.Format(L"Test CString: %s\n", arg);
}
```

Корректный вариант кода должен выглядеть так:

```cpp
s.Format(L"Test CString: %s\n", arg.GetString());
```

Или, как предлагается в [MSDN](https://learn.microsoft.com/en-us/cpp/atl-mfc-shared/cstring-operations-relating-to-c-style-strings?view=msvc-170), для получения указателя на строку можно использовать оператор явного приведения к 'LPCTSTR', реализованный в классе 'CString':

```cpp
void foo()
{
  CString kindOfFruit = "bananas";
  int howmany = 25;
  printf("You have %d %s\n", howmany, (LPCTSTR)kindOfFruit);
}
```

Однако первый вариант 's\.Format\(L"Test CString: %s\\n", arg\);' также является корректным, как и остальные\. Подробнее эта тема обсуждается в статье "[Большой брат помогает тебе](https://pvs-studio.ru/ru/blog/posts/cpp/0073/)"\.

Разработчики MFC реализовали тип 'CString' специальным образом, чтобы его можно было передавать в функции вида 'printf' и 'Format'\. Сделано это достаточно хитро, и те, кто интересуется, могут ознакомиться с реализацией класса 'CStringT'\.

Таким образом, анализатор делает исключение для типа 'CString' и считает следующий код корректным:

```cpp
void foo()
{
  CString s;
  CString arg(L"OK");
  s.Format(L"Test CString: %s\n", arg);
}
```