10

My basic situation: I have an include file that has something like #define foo (Flag1 | Flags2 | Flag3), so it's a predefined combination of bit flags. For the sake of type-safety, I wanted to replace these #defines by static consts, i.e. static const int foo = (Flag1 | Flag2 | Flag3) (or similar). This include file is included in dozens of places in the program.

Now when I'm doing a release build with all relevant optimisation options enabled (using the C++ compiler of VS2010), replacing the #defines seems to increase the executable by a few KiB, depending on how many constants I replaced.

Why does this happen? To my knowledge, integer constants are supposed to be "inlined" into the ASM code that is produced if possible, and I don't see how using a static const vs #define would make a difference here. Clearly, the variable isn't inlined as the disassembly shows:

#define:
01325041  or          eax,0FFD87FE0h
static int:
011E5451  or          eax,dword ptr [CMainFrame::s_TemplateModulePaths+38h (151F008h)]

So the final question is: How can I avoid #define but still rely on the variable being inserted directly into the generated assembly?

j_schultz
  • 649
  • 13
  • 29
  • What are Flag1, Flag2, Flag3? Are those just placeholders you've put here that are actually literals, or are they themselves macros, or constants? – Omaha Aug 02 '12 at 14:19
  • 1
    Are you compiling with optimizations? Why `static`? – R. Martinho Fernandes Aug 02 '12 at 14:19
  • 2
    If those are member variables (which the disassembly suggests), you really need to mention that in the question. – Ben Voigt Aug 02 '12 at 14:20
  • Where are the ints defined? In a header or a .cpp file? – Anon Mail Aug 02 '12 at 14:21
  • @Omaha: Flag1 etc. are enum members defined directly before my #define/static const. – j_schultz Aug 02 '12 at 14:28
  • @R. Martinho Fernandes: As said, all relevant optimizations are enabled, it's a release build. Using static or not using it doesn't make a difference in size, it's just a habit I guess. – j_schultz Aug 02 '12 at 14:28
  • @Ben Voigt: None of the variables are member variables, all of this is happening on module level. The output doesn't suggest that the variables are member variables at all, it just suggests that the integer constant is 38h bytes apart from the "CMainFrame::s_TemplateModulePaths" string constant in the static data section. – j_schultz Aug 02 '12 at 14:28
  • 2
    Try using your constant as an array size. Like `int dummy[foo]`. If the compiler refuses to accept it, you are doing something wrong. I.e. you are somehow turning it into a non-compile-time constant. (BTW, for non-member const declarations `static` is unneseccary. `const` in C++ is already `static` by itself). – AnT stands with Russia Aug 02 '12 at 14:42
  • @AndreyT you're on a lead there. I guess my overloaded operator| for enums is preventing it from being recognized as a constant at this point. It's still strange that it's still reduced to a constant when using `#define`, though. I guess the problem of not being able to declare a pure operator prevents it from working in this case. – j_schultz Aug 02 '12 at 14:51
  • @j_schultz: Yup, no expression containing a function call is considered a compile-time constant integral expression in VS2010. You'll need to wait for a compiler update adding C++11 `constexpr` support. – Ben Voigt Aug 02 '12 at 14:56
  • @j_schultz: Well, if you overloaded the `|` then it is not a constant. However, how could it possible work with `#define`? Something is not right here. You must be doing something else differently in your `#define` version. – AnT stands with Russia Aug 02 '12 at 15:16
  • @AndreyT The overloaded operator| is pure, so it is constant, but without a `constexpr` hint VC++ won't be able to see this in the `const` version as it seems. And really the only difference is is changing `#define` to an actual declaration. – j_schultz Aug 02 '12 at 15:28
  • Are you accidentally taking the address of `foo` anywhere? i.e., are you using it as a variable in some instance, which would cause the compiler to assume it must take up space? – moswald Aug 02 '12 at 16:45
  • @mos: That wouldn't even work with the `#define` version, and no, I'm not doing that. These things are used as bitsets exclusively. – j_schultz Aug 02 '12 at 16:50

5 Answers5

4

There is no reason the compiler couldn't eliminate the static const variable. If you're compiling with optimizations turned on, I'm surprised that VC++ doesn't do that.

I tried compiling this code with gcc.

enum { FLAG1 = 1 << 0, FLAG2 = 1 << 1, FLAG3 = 1 << 2 };

static const int foo = (FLAG1 | FLAG2 | FLAG3);

int main(){
    return foo;
}

With optimizations turned off, it inlined the value but still reserved storage space for the variable.

_main:
LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    call    ___main
    movl    $7, %eax  ;value inlined
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE0:
    .section .rdata,"dr"
    .align 4
__ZL3foo:                     ; storage space for foo
    .long   7

At O2 it inlined the value and got rid of the storage space.

_main:
LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
   .cfi_def_cfa_register 5
    andl    $-16, %esp
    call    ___main
    movl    $7, %eax    ; value inlined
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
   .cfi_endproc
LFE0:
; no storage space for foo

My only thought other that incorrect compiler settings, or VC++ failing to make this optimization, is that the Flags variables might not be compile-time constants which would mean that the value of the expression has to be computed at program start-up, which would prevent inlining.

Dirk Holsopple
  • 8,731
  • 1
  • 24
  • 37
  • As AndreyT pointed out in a comment above, it seems that VC++ fails to recognize the value as being constant, as it is being generated by an overladed and typesafe operator|. – j_schultz Aug 02 '12 at 14:52
0

As seen in the comments, the typesafe operator| overloading for my enums seems to prevent VC++ from inlining the ORed value. I guess I'll keep using the #define version as I hate increasing the executable size if there's no benefits (no, this is not premature optimization) - after all, it doesn't increase readability, and since the combination of flags is already of my flagset enum type, I also don't lose any type-safety, I guess.

j_schultz
  • 649
  • 13
  • 29
  • Storage for the value itself may be optimized away, and the function may be inlined, but the inlined instructions can still take up space. It's little consolation anyway and there's no reason the compiler shouldn't fix it… but MSVC is used in fewer space-critical applications than GCC, so they just might not care enough. – Potatoswatter Aug 03 '12 at 12:54
0
#define ERROR_1 int(1)
#define ERROR_2 int(2)

etc...

and then, you'll have the value directly in the instruction, and you will also have type checking :) and without any optimisation parameter.

In my opinion, the static const int BLABLA=1; stuff is only there to add a namespace to some constants... and use more ram and ram access in the code, what is really pointless when the symbol for a #define constant have to be unique, and when the reference to an error code in memory is less consistent (any malware/bug can modify these values in ram during run time) than the immediate value of this error code, directly in the instruction.

But it is more about modern programming politics... some will claim, use public static const int, and others (like me and Microsoft) prefer the simpler version #define.

Federico Zancan
  • 4,846
  • 4
  • 44
  • 60
  • 1
    You're missing the point because a compiler can do the same optimizations to a `static const int` as it can do to a `#define`; as I have explained in my accepted solution, it simply failed to do this optimization because of my typesafe operator overloads. It could have failed with the `#define` method as well. And what you write about malware is absolute nonsense too, since where do you imagine the value of the define to be stored? In a unicorn that cannot be touched by malware? – j_schultz Mar 18 '14 at 14:44
-2

You can solve your problem by looking at it from another perspective: you may put the data and the functions that use it close together.

In an OO language, this means making your constant foo a private static attribute of a class. This class should have as public methods all the functions that use foo. Being a static private attribute, it will be defined in the .cpp file, and there will be just one instance of it in the executable footprint.

I understand this would be a big change in your code, and not a simple refactorying: surely it's not as simple as modifying the definition of foo from #define to a static global constant. But keeping data and methods that use them close together (i.e.: in the same class) will bring you many non-functional advantages.

Daniele Pallastrelli
  • 2,430
  • 1
  • 21
  • 37
  • That sounds like a lot of useless wrapping that would even bloat the code size more. – KillianDS Aug 02 '12 at 14:43
  • @KillianDS Of course it depends on how you choose the class and its methods. In my experience, it sounds a bit strange that you use a set of flags in many *different* functions: maybe those functions should become methods of a class. Furthermore, flags are usually applied to data. Those data should become data members of the class. Definitely that's not *useless* wrapping, if you do it the right way. – Daniele Pallastrelli Aug 02 '12 at 14:46
  • @KillianDS If you make the flags private static members of your class, you have just one instance of it in all your code: you don't waste any space. – Daniele Pallastrelli Aug 02 '12 at 14:50
  • It could be a better solution, it could be not. Suppose he's simply using it to mask out some bits in a wireline protocol, wrapping that functionality would be quite cumbersome and generally useless. It could very well be that classes are better, but OO is far from the answer to everything, and with the little information about the use case that was given, blindly suggesting OO is just suggesting code bloat imo. – KillianDS Aug 02 '12 at 15:03
  • @KillianDS I never said OO is the answer to everything, but "if you do it OO, do it right". As we don't know what he's using the flags for, we cannot say if my solution is the best, but similarly we cannot say if *your* solution is best. I just gave him an option. Context first of all. – Daniele Pallastrelli Aug 02 '12 at 15:11
  • you are right: context first of all. The context about this question is why a preprocessor constant is value-inlined and a programming constant isn't, it is not about how to restructure the code at all. – KillianDS Aug 02 '12 at 19:53
-2

My guess is that you have static const int foo = (Flag1 | Flag2 | Flag3) in a header file. This causes a version of the constant to be stored in every object file separately. The #define embeds the literal which are all combined by pooling.

Try this, in one C++ file have const int foo = (Flag1 | Flag2 | Flag3), then in the header have extern const int foo.


Update

Sorry if my answer didn't suit your needs. I addressed the question of file size for static const variables.

I guess it's all in what you want. Unless a section of code is causing performance issues, I would never worry about this kind inline of optimization. For me, type safety and file size are things I value above CPU optimization. In addition, an extern const allows for better analysis by coverage and timing tools.

Premature optimization is the root of all evil (or at least most of it) in programming.

Donald Knuth

Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117
  • 6
    This suggestion will completely eliminate the possibility of the constants being "inlined". I.e. this would ensure the exact opposite of what the OP wants. – AnT stands with Russia Aug 02 '12 at 14:40
  • I know about the possibility to use `extern`, but that just makes the code ugly and it doesn't really solve the problem. I'd rather keep using `#define` in that case, as that's guaranteed to be inserted directly in the code. – j_schultz Aug 02 '12 at 14:42
  • @j_schultz I updated my answer. Sorry if it doesn't suit you needs. – Jeffery Thomas Aug 02 '12 at 15:50
  • @Jeffery Thomas: This isn't about premature optimization. But whenenver I see that my executable is getting bigger for no apparent reason, I want to stop it. Other people might say "but it's only 4kb, why would you wonder about that", and my reply is "why should I waste these 4kb if I don't have to" - plus of course, I was interested in why it happens. – j_schultz Aug 02 '12 at 16:52
  • @j_schultz I would be surprised if `extern const int` caused a large change in file size. `static const int` injects the same code into every object file. I don't have access to the MS compiler right now. What happens if you replace `static const int foo = (Flag1 | Flag2 | Flag3)` with `enum { foo = (Flag1 | Flag2 | Flag3) }`. I'm not sure if that gets you any of the type safety you are looking for. – Jeffery Thomas Aug 03 '12 at 20:14