user3282276
user3282276

Reputation: 3804

"Static const" vs "#define" for efficiency in C

I was recently wondering what the difference between #define and static const is in C and why two methods exist to do the same things. I found some people that had similar questions here:

Lots of people talk about best practice and convention as well as give practical reasons for using one over the other, such as the need to pass a pointer to a constant, which I can do with a static const but not with a #define. However I have yet to find anyone talk about a comparison of the efficiency of the two.

From what I understand about the C preprocessor, if I have a statement like this:

#define CONSTANT 6

I create a constant value that can be used like this

char[CONSTANT] which will actually be replaced with this statement char[6] prior to actually being compiled.

This to me seems like it would be more efficient than using a static const constant = 6; because this would create a variable called constant that would live on the stack which I assume would come with some more baggage than a #define. Assuming I need a constant in a situation where I could choose to use either a preprocessor #define or a static const statement with no obvious reasons to choose one over the other, which is more efficient? And how exactly would I go about testing this myself?

Upvotes: 27

Views: 20146

Answers (5)

Venkatesh
Venkatesh

Reputation: 1577

Consider the following 2 test files

Test1.c: Uses static const foo.

// Test1.c uses static const..

#include <stdio.h>

static const foo = 6;

int main() {
    printf("%d", foo);
    return 0;
}

Test2.c: uses macro.

// Test2.c uses macro..

#include <stdio.h>

#define foo 6

int main() {
    printf("%d", foo);
    return 0;
}

and the corresponding assembly equivalences when using gcc -O0(default) are follows,

Assembly for Test1.c:

  0000000000000000 <main>:
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   48 83 ec 20             sub    rsp,0x20
   8:   e8 00 00 00 00          call   d <main+0xd>
   d:   b8 06 00 00 00          mov    eax,0x6
  12:   89 c2                   mov    edx,eax
  14:   48 8d 0d 04 00 00 00    lea    rcx,[rip+0x4]        # 1f <main+0x1f>
  1b:   e8 00 00 00 00          call   20 <main+0x20>
  20:   b8 00 00 00 00          mov    eax,0x0
  25:   48 83 c4 20             add    rsp,0x20
  29:   5d                      pop    rbp
  2a:   c3                      ret
  2b:   90                      nop

Assembly for Test2.c:

  0000000000000000 <main>:
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   48 83 ec 20             sub    rsp,0x20
   8:   e8 00 00 00 00          call   d <main+0xd>
   d:   ba 06 00 00 00          mov    edx,0x6
  12:   48 8d 0d 00 00 00 00    lea    rcx,[rip+0x0]        # 19 <main+0x19>
  19:   e8 00 00 00 00          call   1e <main+0x1e>
  1e:   b8 00 00 00 00          mov    eax,0x0
  23:   48 83 c4 20             add    rsp,0x20
  27:   5d                      pop    rbp
  28:   c3                      ret
  29:   90                      nop

In both the cases, it is not using external memory. But the difference is that, #define replaces foo by the value, static const is an instruction so it increments the instruction pointer to the next instruction and it uses 1 additional register to store the value.

By this, we can say that macro is better than static constants but the difference is minimum.

EDIT: When using -O3 compilation option (i.e at optimization on) both the test1.c and test2.c evaluates the same.

0000000000000000 <main>:
   0:   48 83 ec 28             sub    rsp,0x28
   4:   e8 00 00 00 00          call   9 <main+0x9>
   9:   48 8d 0d 00 00 00 00    lea    rcx,[rip+0x0]        # 10 <main+0x10>
  10:   ba 06 00 00 00          mov    edx,0x6
  15:   e8 00 00 00 00          call   1a <main+0x1a>
  1a:   31 c0                   xor    eax,eax
  1c:   48 83 c4 28             add    rsp,0x28
  20:   c3                      ret
  21:   90                      nop

So, gcc treats both static const and #define as the same when it optimize.

Upvotes: 40

Shafik Yaghmour
Shafik Yaghmour

Reputation: 158469

The quick way to test simple optimization questions is to use godbolt.

For your specific issue a modern optimizing compiler should be able to produce the same code for both cases and will in fact just optimize them away to a constant. We can see this with the following program (see it live):

#include <stdio.h>

#define CONSTANT 6
static const int  constant = 6;

void func()
{
  printf( "%d\n", constant ) ;
  printf( "%d\n", CONSTANT ) ;
}

in both cases both accessing reduce to the following:

movl    $6, %esi    #,

Upvotes: 8

Adam C
Adam C

Reputation: 20

You've totally changed your question. Here's my answer to your new question:

Because we are talking about C, and assuming you are declaring the array on the stack, the answer is actually very interesting. In this case, it is not possible for there to be any difference between the two. The "6" is not actually used at runtime! Because you are only using it to size an array on the stack, the compiler simply uses this to calculate how much stack space is required for the variable.

Suppose you have a 32 bit address space, and your local function contains this 6-byte array (myArray), and an unsigned 32 bit integer (myInt). The compiler creates the following instructions for entering this function: - Write the 4 byte return address to the stack - Move the stack pointer forward by 10 bytes
While executing the function, the runtime doesn't know the names or sizes of any variables. If your code says

myInt = 5;
myArray[myInt] = 25;

then the compiler will have generated these instructions:

- write 00000000 00000000 00000000 00000101 starting at address (StackPointer - 4)
- write 00001101 starting at (StackPointer - 10 + (value at Stackpointer - 4))

So you see, the value "6" is not used at runtime. In fact, you can write to index 6, 7, 8, whatever you want. The run-time won't know that you're overflowing the end of the array. (but depending how you write the code, the compiler may catch the error at compile time)

I glossed over some details there (no doubt some that I'm not even aware of) but that's the gist of it. (I welcome your comments)

Defining the 6 as a "const" may actually cause the value to be stored into 4 bytes of useless space, but that won't affect the execution. Obviosuly it will get optimized away because it is never used.

But, having said all that, never worry about saving a byte of space. Code maintainability is way more important. The risk of introducing a single tiny bug, or making your code a tiny bit less readable, these risks are a trillion trillion times more expensive than the the cost of an extra few bytes or an extra processor cycle. Use constants and enums to take advantage of all the benefits listed here

Upvotes: -2

John Bode
John Bode

Reputation: 123448

static const variables are not (at least should not be) created on the stack; space for them is set aside when the program is loaded, so there should not be a runtime penalty associated with their creation.

There may be a runtime penalty associated with their initialization. although the version of gcc I'm using initializes the constant at compile time; I don't know how common that behavior is. If there is such a runtime penalty, it only occurs once at program startup.

Beyond that, any runtime performance difference between a static const-qualified object and a literal1 (which is what a macro will eventually expand to) should be negligible to non-existent, depending on the type of the literal and the operation involved.

Stupid example (gcc version 4.1.2 20070115 (SUSE Linux)):

#include <stdio.h>

#define FOO_MACRO 5

static const int foo_const = 5;

int main( void )
{
  printf( "sizeof FOO_MACRO = %zu\n", sizeof FOO_MACRO );
  printf( "sizeof foo_const = %zu\n", sizeof foo_const );
  printf( "      &foo_const = %p\n",  ( void * ) &foo_const );

  printf( "FOO_MACRO = %d\n", FOO_MACRO );
  printf( "foo_const = %d\n", foo_const );

  return 0;
}

Output:

sizeof FOO_MACRO = 4
sizeof foo_const = 4
      &foo_const = 0x400660
FOO_MACRO = 5
foo_const = 5

Address of foo_const is in the .rodata section of the binary:

[fbgo448@n9dvap997]~/prototypes/static: objdump -s -j .rodata static

static:     file format elf64-x86-64

Contents of section .rodata:
 40065c 01000200 05000000 73697a65 6f662046  ........sizeof F
                 ^^^^^^^^
 40066c 4f4f5f4d 4143524f 203d2025 7a750a00  OO_MACRO = %zu..
 40067c 73697a65 6f662066 6f6f5f63 6f6e7374  sizeof foo_const
 40068c 203d2025 7a750a00 20202020 20202666   = %zu..      &f
 40069c 6f6f5f63 6f6e7374 203d2025 700a0046  oo_const = %p..F
 4006ac 4f4f5f4d 4143524f 203d2025 640a0066  OO_MACRO = %d..f
 4006bc 6f6f5f63 6f6e7374 203d2025 640a00    oo_const = %d..

Note that the object is already initialized to 5, so there's no runtime initialization penalty.

In the printf statements, the instruction to load the value of foo_const into %esi requires one more byte than the one to load the literal value 0x5, and the instruction has to effectively dereference the %rip register:

400538:       be 05 00 00 00          mov    $0x5,%esi
              ^^^^^^^^^^^^^^
40053d:       bf ab 06 40 00          mov    $0x4006ab,%edi
400542:       b8 00 00 00 00          mov    $0x0,%eax
400547:       e8 e4 fe ff ff          callq  400430 <printf@plt>
40054c:       8b 35 0e 01 00 00       mov    270(%rip),%esi        # 400660 <foo_const>
              ^^^^^^^^^^^^^^^^^
400552:       bf bb 06 40 00          mov    $0x4006bb,%edi
400557:       b8 00 00 00 00          mov    $0x0,%eax
40055c:       e8 cf fe ff ff          callq  400430 <printf@plt>

Will this translate into a measurable runtime performance difference? Maybe, under the right cirucmstances. If you're doing something CPU-bound several hundred thousand times in a tight loop, then yes, using a macro (that resolves to a literal) over a static const variable may be measurably faster. If this is something that happens once over the lifetime of the program, then the difference is too small to measure and there's no compelling reason to use the macro over the static const variable.

As always, correctness and maintainability matter more than performance2. You're less likely to make a mistake using a static const instead of a macro. Consider the following scenario:

#define FOO 1+2
...
x = FOO * 3;

What answer would you expect, and what answer would you get? Compare that with

static const int foo = 1+2;
...
x = foo * 3;

Yes, you could fix the macro case by using parentheses - (1 + 2). The point is, this scenario isn't an issue if you use the static const object. It's one less way to shoot yourself in the foot.


1. For now, I'm only talking about simple scalar literals (integers or floats), not compound literals; haven't investigated their behavior.

2. It doesn't matter how fast your code is if it gives you the wrong answer or does the wrong thing. It doesn't matter how fast your code is if nobody can fix or upgrade it because they can't understand how it works. It doesn't matter how fast your code is if it dies at the first hint of bad input. It doesn't matter how fast your code is if it opens the door to malware.

Upvotes: 0

justin
justin

Reputation: 104698

If the constant's definition is visible to the translation, the compiler is certainly capable of utilizing that as an optimization.

this would create a variable called constant that would live on the stack which I assume would come with some more baggage than a #define.

It could "live" in multiple places. A compiler can certainly substitute the constant where referenced, without requiring static or stack storage.

Assuming I need a constant in a situation where I could choose to use either a preprocessor #define or a static const statement with no obvious reasons to choose one over the other, which is more efficient?

It depends on the compiler and architecture. I get the impression that some people believe #define has a big advantage. It doesn't. The obvious case is a complex evaluation or function call (say sin(4.8). Consider a constant used inside a loop. A properly scoped constant could be evaluated once. A define could evaluate on each iteration.

And how exactly would I go about testing this myself?

Read the assembly produced by each compiler you use, and measure.

If you want a rule of thumb, I would say "Use a constant, unless #define provides you a measurable improvement in the scenario".

There was a good writeup in the GCC docs about this. Maybe somebody remembers where exactly it was.

Upvotes: 4

Related Questions