แชทใหม่
Ctrl
K
ปลั๊กอิน งานตามกำหนดเวลา
Kimi Work Kimi Code
  • ดาวน์โหลดแอป
  • เกี่ยวกับเรา
  • ภาษา
  • รับความช่วยเหลือ

Function Pointer Struct Size Bloat

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, }; ```
ค้นหา
C++ struct function pointer binary size increase C++ function pointer in struct increases compiled size C++ zero size struct function pointer overhead
21 ผลลัพธ์
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
คัดลอก
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
คัดลอก
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 .data instead of .bss or being optimized away entirely
  • The linker now needing to emit relocation entries for the function pointer, since the actual address of INST_End_exec isn'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
คัดลอก
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
คัดลอก
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
คัดลอก
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
คัดลอก
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
คัดลอก
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

ตาราง
คัดลอก
ApproachBinary Cost Per ModuleNotes
Mutable global struct with fn pointer~0.5KB + relocationYour current problem
const / constexpr global struct~8 bytes in .rodataGood fix
Enum + switch dispatch0 bytesBest for fixed mappings
Static lookup table~8 bytes * total opcodesGood for dynamic iteration
static constexpr member0 bytes per instanceCleanest 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).