4

This question is not a duplicate of this one or other similar questions. This question is about clearing a struct after it has been initialized and used already.


Update

After reading the first few of your comments I would like to clarify my question:

  • How can I compel the MSVC compiler to elide the large stack allocation?

I've updated the title, text and the code below to clarify this.


I've recently started to compile my projects with the /GS, /sdl and /analyze compiler options. (Microsoft Visual C++ 2015) With these options, the compiler correctly warns about questionable code constructions. However, I've come across some warnings that I've always thought to be good C++ style.

Please have a look at the following example code:

struct my_struct {
    char  large_member[64000];
};

void do_something_else(my_struct & ms)
{
    // the intent of the next line is to "clear" the ms object
    ms = {};  // <-- here the compiler claims the large stack allocation

   // ... do some more work with ms
}

my_struct oh_my = {}; // construction, apparently no large stack allocation

int main()
{ 
    // ...
    // do something with the oh_my object
    // 

    do_something_else(oh_my);
}

I've been told that the standard C++ way of clearing a struct is the following one:

ms = {};

With the /analyze option, the compiler warns about this in the following way (example):

C:\Dev\MDS\Proj\MDSCPV\Vaps_Common_lib\camber_radar.cpp:162: warning: C6262: Function uses '144400' bytes of stack: exceeds /analyze:stacksize '16384'.. This allocation was for a compiler-generated temporary for 'struct BitmapBuffer' at line 162. Consider moving some data to heap.

I think the following happens:

  • a temporary object is constructed on the stack
  • the temporary object is copied to the object variable

I would like to see something like the default initialization happening there. In my opinion the compiler should be able to optimize the stack-allocation away. But apparently (according to the warning) the compiler does not do this. My question is this: How can I compel the compiler to elide the stack-allocation? I have now started to replace these places with the following code:

std::memset(&ms, 0, sizeof(ms));
HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
user23573
  • 2,479
  • 1
  • 17
  • 36
  • Do you get that warning when building in Release mode (with optimizations on) as well ? – binary01 Dec 11 '18 at 13:50
  • Yes, I get that in release mode. I'm doing a release build with `/GS /analyze` options. (`/GS` and `/sdl` exclude each other to some degree) – user23573 Dec 11 '18 at 13:53
  • 2
    Seems fine to me. You are allocating two structs of at least `64000` bytes each on the stack, compiler has all rights to complain given that threshold was 16384 bytes. – user7860670 Dec 11 '18 at 13:58
  • @user10605163, what do you mean by "can not be elided"? GCC and Clang generate a `memset` call for this line: https://godbolt.org/z/G6CQO_ – Evg Dec 11 '18 at 14:05
  • @Evg Yes, I don't know what I was thinking. –  Dec 11 '18 at 14:06
  • Normal MSVC application will be linked with 1M default stack size. You can tell cl to adjust the analyze option: `/analyze:stacksize1000000` – tunglt Dec 11 '18 at 15:19
  • @tunglt, this is "kill the messenger" not a solution to the problem. – user23573 Dec 11 '18 at 15:32
  • Do you want this particular code to be optimized that way or would an alternative code with the same effect (state afterwards being that `ms` is like default-initialized) and without stack allocation be sufficient? –  Dec 11 '18 at 15:34
  • @user10605163 my concern is "standard correctness" as well as optimal code. I currently think about the `std::memset` method as well as the implementation of a `clear()` member function of the struct. – user23573 Dec 11 '18 at 15:47
  • You should declare a global variable `const my_struct MY_ZERO_ = {};` for example, then in your function where you need to initialize your variable : `ms = MY_ZERO_;` it costs only a memcpy instead of memset. – tunglt Dec 11 '18 at 16:03
  • In principal you can, if you don't want to modify `my_struct` and want to have it work for less trivial cases that cannot be solved by a simple `memset`, construct a new object in `ms`'s storage, e.g. `ms.~my_struct(); new(&ms) my_struct();`. This would be guaranteed to be default constructed without copy, but you need to be careful if you have const or reference data members or base subobjects, in which case one must `std::launder` all pointers/references to the old object. –  Dec 11 '18 at 16:51

1 Answers1

1

Since my_struct is trivially copyable, compilers should be able to place a memset call instead of creating a temporary and then assign it, but it is not mandatory.

A Placement new expression will solve your problem: it constructs an object at a preallocated address using the supplied constructor. For example, new(&ms) my_struct{} gives the same semantics as ms = {}. Should my_struct have a Non trivial Destructor, an explicit call to ms.~my_struct() must precede the placement new. For reference: new expression

I suggest to not use this technique in a normal fashion. It is kind of 'black magic' low level C++. Good compilers should optimize using memset.

By the way, the oh_my global variable doesn't allocate a temporary on the stack because it is Constant initialized at compile time.