For some reason when I assign a function to a struct, my compiled binary size jumps up 0.5kb. I know it is the struct causing this because I can use the function in other parts of my code without it jumping up.
The reason I am storing it in a struct anyways is because I need to iterate over some data & find the relevant function to act on that data. Checking every possibility & calling the specific function name from there doesn't sound fun.
I wouldn't mind the 0.5k if it was just that, but I have a ton of little modules each with their own struct instance holding their own separate function & it stacks up quick.
Here is one of my modules so I can show an example of what EXACTLY I am doing that is causing this behavior:
```
\#pragma once
\#include "../Common.hpp"
\#include "../ScopeState.hpp"
void INST\_End\_exec(const Instruction& inst, const InstToken& token, ScopeState& state, const std::vector<std::string>& args, const std::string& symbol) {
return;
}
Instruction INST\_End {
0,
0,
//INST\_End\_exec,
};
```
Web'de ara
C++ struct function pointer binary size increase C++ function pointer in struct increases compiled size C++ zero size struct function pointer overhead 21 sonuç
Looking at your code, I see the issue. You're storing a function pointer inside a global struct instance, and that struct is being placed in the data section (
.data or .rodata) of your binary — even though the function pointer itself is a compile-time constant.Here's what's happening and how to fix it.
The Problem
When you write:
cpp
Kopyala
Instruction INST_End {
0,
0,
//INST_End_exec, // commented out
};Your
Instruction struct likely has a function pointer field (the third member). When you uncomment it:cpp
Kopyala
Instruction INST_End {
0,
0,
INST_End_exec,
};The compiler emits this global variable into a writable data section (
.data) because function pointers in global mutable structs are treated as runtime-initialized data. Even though the value is known at compile time, the compiler often doesn't place it in .rodata (read-only data) because the struct itself is mutable.The 0.5KB jump isn't the pointer itself (that's 4–8 bytes). It's likely:
- Padding/alignment forcing the struct to be larger than expected
- The struct being placed in
.datainstead of.bssor being optimized away entirely - The linker now needing to emit relocation entries for the function pointer, since the actual address of
INST_End_execisn't known until link time
The Fix: Make It constexpr or const
The cleanest solution is to make the struct
constexpr (or at least const), which allows the compiler to place it in .rodata and potentially inline/optimize it entirely:cpp
Kopyala
struct Instruction {
int opcode;
int arg_count;
void (*exec)(const Instruction&, const InstToken&, ScopeState&,
const std::vector<std::string>&, const std::string&);
};
// Make it constexpr so it lives in .rodata, not .data
constexpr Instruction INST_End {
0,
0,
INST_End_exec,
};If
std::vector or other non-literal types prevent constexpr, use const instead:cpp
Kopyala
const Instruction INST_End { ... };Even Better: Avoid Storing the Pointer at Runtime
Since you mentioned you need to iterate and find the right function — if every
Instruction instance has a fixed, known-at-compile-time function, you don't need to store the pointer in the struct at all. Use a lookup table or template dispatch:Option 1: Enum + Switch (Zero Per-Instance Overhead)
cpp
Kopyala
enum class OpCode { End, Add, Sub, /* ... */ };
struct Instruction {
OpCode opcode;
int arg_count;
// No function pointer here!
};
void execute(const Instruction& inst, /* ... */) {
switch (inst.opcode) {
case OpCode::End: INST_End_exec(inst, ...); break;
case OpCode::Add: INST_Add_exec(inst, ...); break;
// ...
}
}This costs zero bytes per instruction instance — the dispatch is entirely compile-time resolved.
Option 2: Static Const Array (One Pointer Total, Not Per-Instance)
cpp
Kopyala
struct Instruction {
int opcode;
int arg_count;
// No exec pointer here
};
// One global lookup table, not one pointer per instruction
const std::array<void(*)(const Instruction&, const InstToken&,
ScopeState&, const std::vector<std::string>&,
const std::string&), NUM_OPCODES> EXEC_TABLE = {
INST_End_exec,
INST_Add_exec,
// ...
};
// Usage:
EXEC_TABLE[inst.opcode](inst, token, state, args, symbol);Option 3: If You MUST Keep the Struct Layout
Use
static constexpr inside the struct to avoid per-instance storage:cpp
Kopyala
struct Instruction {
int opcode;
int arg_count;
// Static: shared across all instances, stored once in .rodata
static constexpr auto exec = INST_End_exec;
};
// Or use a static method that returns the right function:
static constexpr auto get_executor(OpCode op) {
/* return from lookup table */
}Quick Checklist
Tablolar
Kopyala
| Approach | Binary Cost Per Module | Notes |
|---|---|---|
| Mutable global struct with fn pointer | ~0.5KB + relocation | Your current problem |
const / constexpr global struct | ~8 bytes in .rodata | Good fix |
| Enum + switch dispatch | 0 bytes | Best for fixed mappings |
| Static lookup table | ~8 bytes * total opcodes | Good for dynamic iteration |
static constexpr member | 0 bytes per instance | Cleanest OOP-like approach |
Bottom Line
The 0.5KB isn't the pointer — it's the relocation/metadata overhead of a mutable global with a function pointer. Mark it
constexpr or const, or better yet, remove the pointer from the struct entirely and use a switch or lookup table. With "a ton of little modules," that switch-based approach will save you significant space and likely run faster too (direct calls are inlineable; function pointer calls are not).