﻿# Lesson 10\. Pattern 2\. Functions with variable number of arguments

Typical examples given in most articles on the issues of porting programs to 64\-bit systems refer to incorrect use of the functions _printf_, _scanf_ and their variants\.

Example 1:

```cpp
const char *invalidFormat = "%u";
size_t value = SIZE_MAX;
printf(invalidFormat, value);
```

Example 2:

```cpp
char buf[9];
sprintf(buf, "%p", pointer);
```

In the first case, the programmer does not take into account that the type [size\_t](https://pvs-studio.com/en/blog/terms/0044/) is not equivalent to the type _unsigned_ on a 64\-bit platform\. It will result in printing an incorrect result if value \> UINT\_MAX\.

In the second case, the author does not take into account that the size of the pointer might be more than 32 bits in future\. As a result, this code will cause a buffer overflow on the 64\-bit architecture\.

Incorrect use of functions with the variable number of arguments is a common error not only for 64\-bit architectures but for all architectures\. It is explained by the fundamental danger of using these C\+\+ constructs\. It is generally accepted to refuse using them and resort to safe programming methods\. We highly recommend you to modify your code and employ safe methods\. For example, you may replace _printf_ with _cout_, _sprintf_ with _boost::format_ or _std::stringstream_\.

This recommendation is often criticized by Linux developers who argue that gcc compiler checks if the format string corresponds to the actual arguments passed into the function _printf_\. But they forget that the format string may be called from other program parts or loaded from resources\. In other words, the format string is seldom present explicitly in the code of a real program and therefore the compiler cannot check it\. If developers use Visual Studio 2005/2008 they will not be able to get the warning on the code like "void \*p \= 0; printf\("%x", p\);" even using the switches _/W4_ and _/Wall_\.

There exist size specifiers to work with memsize\-types in functions like _sscanf_, _printf_\. If you are developing a Windows application, you may use the "I" size specifier\. For example:

```cpp
size_t s = 1; 
printf("%Iu", s);
```

If you are developing a Linux application, you may try the size specifier "z"\. For example:

```cpp
size_t s = 1;
printf("%zu", s);
```

The specifiers are well described in the Wikipedia article "[printf](https://en.wikipedia.org/wiki/Printf_format_string)"\.

If you have to support a code being ported that uses functions like _sscanf_, you may employ special macros in the format of control strings that expand into the necessary size specifiers\. Here is an example of a macro that helps create a portable code for various systems:

```cpp
// PR_SIZET on Win64 = "I"
// PR_SIZET on Win32 = ""
// PR_SIZET on Linux64 = "z"
// ...
size_t u;
scanf("%" PR_SIZET "u", &u);
```

Here is one more example\. Although it looks most strange, the code given here in an abridged form was used in a real application in the UNDO/REDO subsystem:

```cpp
// Here the pointers were saved in the form of a string
int *p1, *p2;
....
char str[128];
sprintf(str, "%X %X", p1, p2);
// In another function this string was processed
// in this way:
void foo(char *str)
{
  int *p1, *p2;
  sscanf(str, "%X %X", &p1, &p2);
  // The result is incorrect values of pointers p1 and p2.
  ...
}
```

Manipulation with the pointers using "%X" resulted in an incorrect program behavior on a 64\-bit system\. This example shows how dangerous may be the depths of large and complex projects written for many years\. If your project is rather large and obsolete, you might encounter very interesting fragments like this one\.

## Diagnosis

Those types that change their sizes on a 64\-bit system, i\.e\. [ memsize\-types](https://pvs-studio.com/en/blog/terms/0030/), are dangerous for the functions with the variable number of arguments\. [PVS\-Studio](https://pvs-studio.com/en/pvs-studio/) static analyzer warns the programmer about such types with the help of the [V111](https://pvs-studio.com/en/docs/warnings/v111/) diagnostic warning\.

If the types of the arguments have not changed their sizes, the code is considered correct and no warnings are generated\. Here is an example of code correct from the analyzer's viewpoint:

```cpp
printf("%d", 10*5);
CString str;
size_t n = sizeof(float);
str.Format(StrFormat, static_cast<int>(n));
```

_The course authors: Andrey Karpov \([karpov@viva64\.com](mailto:karpov@viva64.com)\), Evgeniy Ryzhkov \([evg@viva64\.com](mailto:evg@viva64.com)\)\._

_The rightholder of the course "Lessons on development of 64\-bit C/C\+\+ applications" is OOO "Program Verification Systems"\. The company develops software in the sphere of source program code analysis\. [The company's site](https://pvs-studio.com/en/)\._