Compiler Warning C4789

Compiler Warning C4789

Rate This
  • Comments 11

When Visual Studio 2010 ships, it will have improvements to warning C4789; allowing it to catch more cases of buffer overrun. This blog post will cover what C4789 warns about, and how to resolve the warning.

What does C4789 mean?

 

When compiling your source file, you may receive the warning: “warning C4789: destination of memory copy is too small.”

This message means that the compiler has detected a possible buffer overrun in your code.

Example 1

Let’s say we have the source file a.cpp that contains the following:

1: #include <memory.h>

2:

3: int p[1];

4:

5: void bar() {

6:     memset(&p[1], 1, sizeof(int));

7: }

 

From the ”Visual Studio 2008 Command Prompt”, if you compile this with the command:

cl /c /O2 a.cpp

You will receive the warning:

a.cpp(6) : warning C4789: destination of memory copy is too small

For the example above, the compiler has detected a buffer overrun for the variable ‘p’. 'p' has been allocated as an array with one element. Arrays are zero-indexed, so the memset on line 6 is taking the address of the second element of an array; this means that we are actually writing to memory outside the array, corrupting memory!

In this case, the user most likely meant to memset the first element, and thus to fix this issue, the memset would be changed to

memset(&p[0], 1, sizeof(int));

 

Typical User Scenarios

 

In practice, a lot of buffer overruns will not be as obvious as Example 1, so I’ll provide some more examples to help you in your investigations.

Example 2

Let’s say we have the source file a.cpp that contains the following:

1:  short G1;

2:

3:  void foo(int * x)

4:  {

5:      *x = 5;

6:  }

7:

8:  void bar() {

9:     foo((int *)&G1);

10: }

From the ”Visual Studio 2010 Command Prompt”, if you compile this with the command:

cl /c /O2 a.cpp

You will receive the warning:

a.cpp(9) : warning C4789: destination of memory copy is too small

In this example, we’ve created a variable 'G1' of size short (which is only two bytes), but we’ve taken the address of it and casted it to 'int *' to pass to 'foo’. 'foo' then writes 4 bytes to the memory location pointed at by 'x’. As 'G1' is only 2 bytes in length, the store “*x = 5” will write past 'G1', resulting in a buffer overrun.

There are a couple of important things to note about this example. This buffer overrun will only be caught with the improvements made in Visual Studio 2010. Also, this warning is caught by inlining 'foo' into 'bar'. This means that this buffer overrun is only caught when optimizations are enabled.

To fix the buffer overrun in Example 2, we declare 'G1' as int. If that isn’t an option, we can create a new variable to pass to ‘foo,’ and assign that variable to ‘G1’ (which truncates the int to a short):

   int y;

   foo(&y);

   G1 = y;

 

Example 3

Let’s say we have the source file a.cpp that contains the following:

1:  int G1;

2:  int G2;

3:

4:  void foo(int ** x)

5:  {

6:      *x = &G2;

7:  }

8:

9:  void bar() {

10:     foo((int *)&G1);

11: }

From the ”Visual Studio 2010 X64 Cross Tools Command Prompt”, if you compile this with the command:

cl /c /O2 a.cpp

You will receive the warning:

a.cpp(10) : warning C4789: destination of memory copy is too small

This example is exactly like Example 2 with a key difference. We’ve casted “int” to “int *”. On x86, this is a harmless cast (int and int * are the same size, 4 bytes). However, on x64, “int” is 4 bytes, and “int *” is 8 bytes, so this code is no longer correct when this code is run on x64.

C4789 False Positives

 

You may hit cases of C4789 where the warning is incorrect. This can happen because the compiler detects a buffer overrun along a code path that will never fire.

Example 4

1:  __int64 G1;

2:  int lengthOfG1 = 8;

3:

4:  void foo(char * x, int len) {

5:      if (len > 8) {

6:          x[8] = 1;

7:      }

8:  }

9:

10: void bar() {

11:     foo((char *)&G1, lengthOfG1);

12: }

From the ”Visual Studio 2010 Command Prompt”, if you compile this with the command:

cl /c /O2 a.cpp

You will receive the warning:

a.cpp(11) : warning C4789: destination of memory copy is too small

In this example, the compiler thinks that ‘G1’ can be buffer overrun because of “x[8] = 1” would assign outside of the size of ‘G1.’ However, as long as ‘lengthOfG1’ is the correct length of ‘G1,’ “x[8] = 1” will never fire for ‘G1,’ and thus a buffer overrun will never occur.

For some of these false positives, the only option will be to disable the warning. In this particular example, however, changing "int lengthOfG1 = 8” to

const int lengthOfG1 = 8;

would solve the problem.

Workarounds

 

If you have proven that the warning is a false positive, there are a couple of different ways to disable the warning.

1.       Disable the warning for one function (recommended)

2.       Disable the warning for all functions

Disable the warning for one function

The compiler allows you to disable a warning for a particular function. This is done by putting

#pragma warning ( disable : 4789 )

before the function, and putting

#pragma warning ( default : 4789 )

after the function. This will disable the warning (in this case warning 4789) for that function (and any functions which inline it).

Example 5

1:  __int64 G1;

2:  int lengthOfG1 = 8;

3:

4:  #pragma warning ( disable : 4789 )

5:  void foo(char * x, int len) {

6:      if (len > 8) {

7:          x[8] = 1;

8:      }

9:  }

10: #pragma warning ( default : 4789 )

11:

12: void bar() {

13:     foo((char *)&G1, lengthOfG1);

14: }

15:

16: void bar1() {

17:     foo((char *)&G1, 9);

18: }

With the #pragma around ‘foo,’ you will receive no warnings; while without it you will receive the warnings:

a.cpp(13) : warning C4789: destination of memory copy is too small

a.cpp(17) : warning C4789: destination of memory copy is too small

You can also choose to disable the warning for one of the functions where the warning occurs. In the example above, we could put the #pragma around ‘bar’ instead of ‘foo’, and then we’d eliminate the warning for line 13, but still receive the warning on line 17.

Disable the warning for all functions

If you need to ignore warning 4789 completely, you can specify /wd4789 on the command line.

cl /c /O2 /wd4789 a.cpp

This option isn’t recommended as it will hide potentional buffer overruns in your code.

 

  • Here's the correct way to selectively disable a warning:

    #pragma warning(push)

    #pragma warning(disable: NNNN)

    YOUR CODE HERE

    #pragma warning(pop)

    push-disable-pop is superior to disable-default for a simple reason: if warning NNNN has already been disabled (via /wd or #pragma), push-disable-pop will leave it disabled, while disable-default will leave it ENABLED.

  • Seeing as in your example are using cpp extension files why do you not show some good C++ examples using good style guides.

    memory.h should be <memory>

    How about using std::fill instead of memset

    How about using C++ style casts instead of C

  • foobar, the problems demonstrated in the examples are not normally present in code written in "good C++". So using "C-style C++" actually makes sense here.

  • Please make C++ and std:: the new normal then.

  • I really hope Example 3 generates an error, not warning C4789.  Comeau has this right:

    Comeau C/C++ 4.3.10.1 (Oct  6 2008 11:28:09) for ONLINE_EVALUATION_BETA2

    Copyright 1988-2008 Comeau Computing.  All rights reserved.

    MODE:strict errors C++ C++0x_extensions

    line 10: error: argument of type "int *" is incompatible with

             parameter of type "int **"

       foo((int *)&G1);

           ^

    1 error detected in the compilation.

  • I'm going to guess it's a typo.  The code should probably force a cast to int **.  This then correctly shows the case of memory allocated to an int (4 bytes), being cast into a type (int *) that requires 8 bytes on a 64-bit platform.  (Of course, this example assumes your 64-bit platform doesn't just use 64-bit ints :-) )

    void bar() {

     foo((int **)&G1);

    }

    Then Comeau compiles it just fine, which is expected.

  • "(Of course, this example assumes your 64-bit platform doesn't just use 64-bit ints :-) )"

    Which it is not going to happen as Windows uses the LLP64 data model.

  • There is one more typical example as below:

    int *p = new int[Size]; //size comes from input

    int *iter = p;

    for(int i=0; i<=Size; i++)

     *(iter++) = 0; //Overrun happen when i equal to Size

    do you guys know if this condition can be catched by compiler? it is really cool if VS2010 can handle this.

  • Regarding example 3: Yes the (int *) on line 10 is a typo, I meant (int **).

    If I compile the example 3 with the typo, I will also get a type failure:

    c.cpp(18) : error C2664: 'foo' : cannot convert parameter 1 from 'int *' to 'int **'

  • Regarding the "int *p = new int[size]..." example:

    Although a good suggestion, the example you gave will not produce a 4789 warning. Currently, the warning is only for buffer overruns of variables whose size is known at compile time.

  • @wangli.chao come on this a one liner which done correctly is:

    std::vector<int>foo(size,0);

Page 1 of 1 (11 items)