[{"content":" random c++ stuff that might be leeched off the internet std::shared_ptr\u0026lt;void\u0026gt; writing an allocator generating switch statements with templates counting the number of member fields of a trivial struct OmegaException destructors should almost never throw exceptions defining a static class member Probably prefer (... \u0026lt;op\u0026gt; Ts) to (Ts \u0026lt;op\u0026gt; ...\u0026gt;) type hack Separator argument delete move constructor strange syntax getting the offset of a base class from a derived class Calendar tricks random c++ stuff that might be leeched off the internet std::shared_ptr\u0026lt;void\u0026gt; since shared_ptr stores a type-erased deleter, you can do the following\nvoid foo() { std::shared_ptr\u0026lt;void\u0026gt; p(new A); p.reset(new B); // ~A() called here } // ~B() called here writing an allocator signature of an allocator that will work with std::vector\u0026lt;int, myalloc\u0026lt;int\u0026gt;\u0026gt;\ntemplate \u0026lt;typename T\u0026gt; struct myalloc { using value_type = T; T* allocate(size_t n) {...} void deallocate(T*, size_t n) {...} // probably delete copy ctor and copy assign too }; generating switch statements with templates from my stack overflow question\ntemplate \u0026lt;int ...Key\u0026gt; void foo(int key) { switch (key) { case 1: bar\u0026lt;1\u0026gt;();break; case 3: bar\u0026lt;3\u0026gt;();break; case 5: bar\u0026lt;5\u0026gt;();break; case 7: bar\u0026lt;7\u0026gt;();break; case 9: bar\u0026lt;9\u0026gt;();break; default: break; } } can be re-written as\ntemplate \u0026lt;int ...Keys\u0026gt; void foo(int key) { ([\u0026amp;]() { if (key == Keys) { bar\u0026lt;Keys\u0026gt;(); return true; } return false; }() || ...); } counting the number of member fields of a trivial struct struct X { int a; int b; }; struct OmegaType { template \u0026lt;typename T\u0026gt; operator T() {} }; template \u0026lt;typename T\u0026gt; constexpr size_t count_member(auto... args) { if constexpr (requires {T{args...};} == false) { return sizeof...(args) - 1; } else { return count_member\u0026lt;T\u0026gt;(args..., OmegaType{}); } } static_assert(count_member\u0026lt;X\u0026gt;() == 2); OmegaException From cppcon\n#include \u0026lt;string\u0026gt; #include \u0026lt;source_location\u0026gt; #include \u0026lt;iostream\u0026gt; template \u0026lt;typename DATA_T\u0026gt; class OmegaException { public: OmegaException( std::string str, DATA_T data, const std::source_location\u0026amp; loc = std::source_location::current() // std::stacktrace trace = std::stacktrace::current() ) : err_str_{std::move(str)}, user_data_{std::move(data)}, location_(loc) // backtrace_(trace) {} std::string\u0026amp; what() {return err_str_;} const std::string\u0026amp; what() const noexcept { return err_str_; } const std::source_location\u0026amp; where() const noexcept { return location_; } // const std::stacktrace\u0026amp; stack() const noexcept { return backtrace_; } DATA_T\u0026amp; data() { return user_data_; } const DATA_T\u0026amp; data() const noexcept { return user_data_; } private: std::string err_str_; DATA_T user_data_; const std::source_location location_; // const std::stacktrace backtrace_; }; int main() { try { throw OmegaException\u0026lt;int\u0026gt;(\u0026#34;oh no\u0026#34;, 5); } catch (const OmegaException\u0026lt;int\u0026gt;\u0026amp; e) { std::cout \u0026lt;\u0026lt; e.where().file_name() \u0026lt;\u0026lt; std::endl; } } destructors should almost never throw exceptions If an exception is thrown during an exception handling, std::terminate is triggered. Objects are popped off the stack and destructed during stack rewinding, and if one of these puppies throw, your program dies! See isocpp faq.\ndefining a static class member A declaration of a struct variable within the struct definition don\u0026rsquo;t define the struct. An external definition is required.\nstruct S { static int X = 5; // declares, not defines }; int S::X; // defines int main() { int y = S::X; // S::X is not not odr-use v.push_back(S::X); // push_back takes a reference - S::X is odr-use, requires a definition } Probably prefer (... \u0026lt;op\u0026gt; Ts) to (Ts \u0026lt;op\u0026gt; ...\u0026gt;) if op isn\u0026rsquo;t a commutative operator, (Ts \u0026lt;op\u0026gt; ...) probably don\u0026rsquo;t do what you expect.\ntemplate\u0026lt;int... Ts\u0026gt; int foo() { return (Ts - ...); } template\u0026lt;int... Ts\u0026gt; int bar() { return (... - Ts); } // foo\u0026lt;10,2,3\u0026gt;() -\u0026gt; 11 // bar\u0026lt;10,2,3\u0026gt;() -\u0026gt; 5 type hack Sadly the commitee hated this and swore to break this some day.\n#include \u0026lt;string\u0026gt; #include \u0026lt;type_traits\u0026gt; template\u0026lt;int N\u0026gt; struct tag{}; template\u0026lt;typename T, int N\u0026gt; struct loophole_t { friend auto loophole(tag\u0026lt;N\u0026gt;) { return T{}; }; }; auto loophole(tag\u0026lt;0\u0026gt;); auto loophole(tag\u0026lt;1\u0026gt;); int main() { sizeof( loophole_t\u0026lt;std::string, 0\u0026gt; ); sizeof( loophole_t\u0026lt;int, 1\u0026gt; ); static_assert(std::is_same_v\u0026lt;std::string, decltype( loophole(tag\u0026lt;0\u0026gt;{}) ) \u0026gt;); static_assert(std::is_same_v\u0026lt;int, decltype( loophole(tag\u0026lt;1\u0026gt;{}) ) \u0026gt;); } Separator argument Have a function that takes in a char as separator? Make it a template arument to save one register!\ntemplate \u0026lt;char c\u0026gt; void foo(const std::vector\u0026lt;std::string\u0026gt;\u0026amp; v) { // \u0026lt;some concat logic maybe?\u0026gt; } delete move constructor This is an extension of the rule of 5. Specifically, X WILL NOT generate a move constructor but Y will.\nstruct X { virtual ~X() = default; // no move ctor generated due to user defined dtor. }; struct Y : X { // move ctor is generated since there is no user defined dtor. }; strange syntax void foo(int fn(double)) // fn is a pointer-to-function getting the offset of a base class from a derived class struct A { int a1, a2, a3; }; struct B { int b; }; struct C : A, B { int c, c1; }; template \u0026lt;typename Parent, typename Child\u0026gt; consteval size_t getoffset() { union {Child c; char b[sizeof(Child)];} u; auto* a1 = (static_cast\u0026lt;void*\u0026gt;(std::addressof(static_cast\u0026lt;Parent\u0026amp;\u0026gt;(u.c)))); for (int i = 0; i \u0026lt; sizeof(Child); ++i) { auto* a2 = (static_cast\u0026lt;void*\u0026gt;(std::addressof(u.b[i]))); if (a1 == a2) { return i; } } } static_assert(getoffset\u0026lt;A, C\u0026gt;() == 0); static_assert(getoffset\u0026lt;B, C\u0026gt;() == 12); Calendar tricks days in month // compute day of a month days_in_month = [](int month) { return ((month \u0026gt; 3) ^ month) \u0026amp; 30; }; // compute day of a month is_leap_year = [](int year) -\u0026gt; bool { return (year % 25) == 0 ? (year % 16) == 0 : (year % 4) == 0; }; // perform year % 25 first since its easily predictable // if so: check if its divisible by 16 // else: simply check if its divisible by 4 // these should compile to // year % 25 // converted to an imul, add and cmp, which is relatively cheap (5 cycles) // ? : // converted to cmov ","permalink":"https://blog.weineng.me/posts/cpp_tidbits/","summary":"\u003c!-- raw HTML omitted --\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"#random-c-plus-plus-stuff-that-might-be-leeched-off-the-internet\"\u003erandom c++ stuff that might be leeched off the internet\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"#std-shared-ptr-void\"\u003estd::shared_ptr\u0026lt;void\u0026gt;\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#writing-an-allocator\"\u003ewriting an allocator\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#generating-switch-statements-with-templates\"\u003egenerating switch statements with templates\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#counting-the-number-of-member-fields-of-a-trivial-struct\"\u003ecounting the number of member fields of a trivial struct\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#omegaexception\"\u003eOmegaException\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#destructors-should-almost-never-throw-exceptions\"\u003edestructors should almost never throw exceptions\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#defining-a-static-class-member\"\u003edefining a static class member\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#probably-prefer--dot-dot-dot-op-ts--to--ts-op-dot-dot-dot\"\u003eProbably prefer \u003ccode\u003e(... \u0026lt;op\u0026gt; Ts)\u003c/code\u003e to \u003ccode\u003e(Ts \u0026lt;op\u0026gt; ...\u0026gt;)\u003c/code\u003e\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#type-hack\"\u003etype hack\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#separator-argument\"\u003eSeparator argument\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#delete-move-constructor\"\u003edelete move constructor\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#strange-syntax\"\u003estrange syntax\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#getting-the-offset-of-a-base-class-from-a-derived-class\"\u003egetting the offset of a base class from a derived class\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#calendar-tricks\"\u003eCalendar tricks\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c!-- raw HTML omitted --\u003e\n\u003ch2 id=\"random-c-plus-plus-stuff-that-might-be-leeched-off-the-internet\"\u003erandom c++ stuff that might be leeched off the internet\u003c/h2\u003e\n\u003ch3 id=\"std-shared-ptr-void\"\u003estd::shared_ptr\u0026lt;void\u0026gt;\u003c/h3\u003e\n\u003cp\u003esince shared_ptr stores a type-erased deleter, you can do the following\u003c/p\u003e","title":"C++ tidbits"},{"content":"I thought of a cute problem: what is the smallest (size) ./a.out binary I can create?\nHere are some rules the program should follow:\n./a.out must run successfully. $? must deterministically be 0. The binary must be produced by GCC only; no post-processing with objcopy, hex editors, or manual patching. We begin with the simplest program possible:\n// compiled with gcc empty.c int main() { return 0; } This gives us a file size of 15816 bytes (from stat). Not too shabby, but we will need four of the RAM used in the Apollo guidance computer to fit our binary that does nothing.\nLooking at file:\n❯ file a.out a.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /nix/store/jms7zxzm7w1whczwny5m3gkgdjghmi2r-glibc-2.42-51/lib/ld-linux-x86-64.so.2, for GNU/Linux 3.10.0, not stripped not stripped looks suspicious. Whatever it is, surely it is better if we can strip stuff out of our binary. It turns out that gcc provides a -s flag that compiles the code without retaining any debugging information. We are now at 14352 bytes with our code stripped.\nBetween running ./a.out and hitting int main(), there are many sorceries happening behind the scenes - so much so that there was a one-hour talk by Matt Godbolt at cppcon about it. Let’s tweak the main function so that we have a freestanding binary that skips everything that happened before int main().\n// compiled with gcc empty.c -s -nostartfiles #include \u0026lt;cstdlib\u0026gt; extern \u0026#34;C\u0026#34; __attribute((noreturn)) void _start() { exit(0); } This only gives us a measly improvement to 13632 bytes. Given how much Matt complains is happening before int main, surely there is still code in there that we aren’t running but is still in our binary!\nChecking objdump -x a.out, we can see a bunch of libraries being dynamically loaded:\n❯ objdump -x a.out a.out: file format elf64-x86-64 a.out architecture: i386:x86-64, flags 0x00000150: HAS_SYMS, DYNAMIC, D_PAGED start address 0x0000000000001020 Program Header: PHDR off 0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3 filesz 0x00000000000002a0 memsz 0x00000000000002a0 flags r-- INTERP off 0x00000000000002e0 vaddr 0x00000000000002e0 paddr 0x00000000000002e0 align 2**0 filesz 0x0000000000000053 memsz 0x0000000000000053 flags r-- LOAD off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12 filesz 0x00000000000004a8 memsz 0x00000000000004a8 flags r-- LOAD off 0x0000000000001000 vaddr 0x0000000000001000 paddr 0x0000000000001000 align 2**12 filesz 0x000000000000002e memsz 0x000000000000002e flags r-x LOAD off 0x0000000000002000 vaddr 0x0000000000002000 paddr 0x0000000000002000 align 2**12 filesz 0x000000000000007c memsz 0x000000000000007c flags r-- LOAD off 0x0000000000002e78 vaddr 0x0000000000003e78 paddr 0x0000000000003e78 align 2**12 filesz 0x0000000000000190 memsz 0x0000000000000190 flags rw- DYNAMIC off 0x0000000000002e78 vaddr 0x0000000000003e78 paddr 0x0000000000003e78 align 2**3 filesz 0x0000000000000170 memsz 0x0000000000000170 flags rw- NOTE off 0x0000000000000338 vaddr 0x0000000000000338 paddr 0x0000000000000338 align 2**3 filesz 0x0000000000000030 memsz 0x0000000000000030 flags r-- 0x6474e553 off 0x0000000000000338 vaddr 0x0000000000000338 paddr 0x0000000000000338 align 2**3 filesz 0x0000000000000030 memsz 0x0000000000000030 flags r-- EH_FRAME off 0x0000000000002000 vaddr 0x0000000000002000 paddr 0x0000000000002000 align 2**2 filesz 0x000000000000001c memsz 0x000000000000001c flags r-- STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4 filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw- RELRO off 0x0000000000002e78 vaddr 0x0000000000003e78 paddr 0x0000000000003e78 align 2**0 filesz 0x0000000000000188 memsz 0x0000000000000188 flags r-- Dynamic Section: NEEDED libc.so.6 RUNPATH /nix/store/jms7zxzm7w1whczwny5m3gkgdjghmi2r-glibc-2.42-51/lib:/nix/store/ab3753m6i7isgvzphlar0a8xb84gl96i-gcc-15.2.0-lib/lib HASH 0x0000000000000368 GNU_HASH 0x0000000000000380 STRTAB 0x00000000000003d0 SYMTAB 0x00000000000003a0 STRSZ 0x0000000000000099 SYMENT 0x0000000000000018 DEBUG 0x0000000000000000 PLTGOT 0x0000000000003fe8 PLTRELSZ 0x0000000000000018 PLTREL 0x0000000000000007 JMPREL 0x0000000000000490 FLAGS_1 0x0000000008000000 VERNEED 0x0000000000000470 VERNEEDNUM 0x0000000000000001 VERSYM 0x000000000000046a Version References: required from libc.so.6: 0x09691a75 0x00 02 GLIBC_2.2.5 Sections: Idx Name Size VMA LMA File off Algn 0 .interp 00000053 00000000000002e0 00000000000002e0 000002e0 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .note.gnu.property 00000030 0000000000000338 0000000000000338 00000338 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .hash 00000014 0000000000000368 0000000000000368 00000368 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .gnu.hash 0000001c 0000000000000380 0000000000000380 00000380 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .dynsym 00000030 00000000000003a0 00000000000003a0 000003a0 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .dynstr 00000099 00000000000003d0 00000000000003d0 000003d0 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 6 .gnu.version 00000004 000000000000046a 000000000000046a 0000046a 2**1 CONTENTS, ALLOC, LOAD, READONLY, DATA 7 .gnu.version_r 00000020 0000000000000470 0000000000000470 00000470 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 8 .rela.plt 00000018 0000000000000490 0000000000000490 00000490 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 9 .plt 00000020 0000000000001000 0000000000001000 00001000 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 10 .text 0000000e 0000000000001020 0000000000001020 00001020 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE 11 .eh_frame_hdr 0000001c 0000000000002000 0000000000002000 00002000 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 12 .eh_frame 0000005c 0000000000002020 0000000000002020 00002020 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 13 .dynamic 00000170 0000000000003e78 0000000000003e78 00002e78 2**3 CONTENTS, ALLOC, LOAD, DATA 14 .got.plt 00000020 0000000000003fe8 0000000000003fe8 00002fe8 2**3 CONTENTS, ALLOC, LOAD, DATA 15 .comment 00000012 0000000000000000 0000000000000000 00003008 2**0 CONTENTS, READONLY SYMBOL TABLE: no symbols Since we have a dynamic section, our binary needs to store the stubs needed to load the dynamic section. At this point, the binary is still dynamically linked. It contains an interpreter path, dynamic symbol tables, relocation metadata, PLT/GOT machinery, and references to shared libraries. That is a lot of infrastructure for a program whose only ambition is to immediately exit. So let’s remove three big pieces:\n-nostdlib: do not link the standard libraries. -static: avoid dynamic linking machinery. -no-pie: produce a fixed-address executable instead of a position-independent one. // compiled with gcc -static -nostdlib -no-pie -s empty.c extern \u0026#34;C\u0026#34; __attribute__((noreturn)) void _start() { __asm__ volatile( \u0026#34;mov $60, %%eax\\n\u0026#34; // SYS_exit \u0026#34;xor %%edi, %%edi\\n\u0026#34; // exit status 0 \u0026#34;syscall\\n\u0026#34; :: : \u0026#34;rax\u0026#34;, \u0026#34;rdi\u0026#34;); __builtin_unreachable(); } We are now at 8704 bytes.\nLooking at objdump again (this time using objdump -D a.out to see all sections):\n❯ objdump -D a.out a.out: file format elf64-x86-64 Disassembly of section .note.gnu.property: 0000000000400190 \u0026lt;.note.gnu.property\u0026gt;: 400190:\t04 00 add $0x0,%al 400192:\t00 00 add %al,(%rax) 400194:\t20 00 and %al,(%rax) 400196:\t00 00 add %al,(%rax) 400198:\t05 00 00 00 47 add $0x47000000,%eax 40019d:\t4e 55 rex.WRX push %rbp 40019f:\t00 01 add %al,(%rcx) 4001a1:\t00 01 add %al,(%rcx) 4001a3:\tc0 04 00 00 rolb $0x0,(%rax,%rax,1) 4001a7:\t00 01 add %al,(%rcx) 4001a9:\t00 00 add %al,(%rax) 4001ab:\t00 00 add %al,(%rax) 4001ad:\t00 00 add %al,(%rax) 4001af:\t00 02 add %al,(%rdx) 4001b1:\t00 01 add %al,(%rcx) 4001b3:\tc0 04 00 00 rolb $0x0,(%rax,%rax,1) 4001b7:\t00 01 add %al,(%rcx) 4001b9:\t00 00 add %al,(%rax) 4001bb:\t00 00 add %al,(%rax) 4001bd:\t00 00 add %al,(%rax) ... Disassembly of section .text: 0000000000401000 \u0026lt;.text\u0026gt;: 401000:\t55 push %rbp 401001:\t48 89 e5 mov %rsp,%rbp 401004:\tb8 3c 00 00 00 mov $0x3c,%eax 401009:\t31 ff xor %edi,%edi 40100b:\t0f 05 syscall Disassembly of section .eh_frame: 0000000000402000 \u0026lt;.eh_frame\u0026gt;: 402000:\t14 00 adc $0x0,%al 402002:\t00 00 add %al,(%rax) 402004:\t00 00 add %al,(%rax) 402006:\t00 00 add %al,(%rax) 402008:\t01 7a 52 add %edi,0x52(%rdx) 40200b:\t00 01 add %al,(%rcx) 40200d:\t78 10 js 0x40201f 40200f:\t01 1b add %ebx,(%rbx) 402011:\t0c 07 or $0x7,%al 402013:\t08 90 01 00 00 18 or %dl,0x18000001(%rax) 402019:\t00 00 add %al,(%rax) 40201b:\t00 1c 00 add %bl,(%rax,%rax,1) 40201e:\t00 00 add %al,(%rax) 402020:\te0 ef loopne 0x402011 402022:\tff (bad) 402023:\tff 0d 00 00 00 00 decl 0x0(%rip) # 0x402029 402029:\t41 0e rex.B (bad) 40202b:\t10 86 02 43 0d 06 adc %al,0x60d4302(%rsi) 402031:\t00 00 add %al,(%rax) ... Disassembly of section .comment: 0000000000000000 \u0026lt;.comment\u0026gt;: 0:\t47 rex.RXB 1:\t43 rex.XB 2:\t43 3a 20 rex.XB cmp (%r8),%spl 5:\t28 47 4e sub %al,0x4e(%rdi) 8:\t55 push %rbp 9:\t29 20 sub %esp,(%rax) b:\t31 35 2e 32 2e 30 xor %esi,0x302e322e(%rip) # 0x302e323f ... Some of you might wonder why there is a .comment section. We are trying to rid our binary of as many things as possible, and we definitely don\u0026rsquo;t want comments.\nTurns out, the .comment section stores the compiler used to create the binary and in our case, GCC: (GNU) 15.2.0 (hex: 47 43 43 3a …). However, objdump interprets it as assembly, hence we get those strange instructions. Adding -fno-ident to gcc removes the .comment section and boosts us to 8616 bytes.\nThe astute amongst you would also spot the .eh_frame section. That pesky section is used for stack unwinding, and our program that does nothing doesn’t need it for error handling. By telling GCC -fno-exceptions -fno-asynchronous-unwind-tables, we get to 4408 bytes.\nThe last section that we will want to remove is the .note.gnu.property section.\n❯ readelf -n a.out Displaying notes found in: .note.gnu.property Owner Data size Description GNU 0x00000020\tNT_GNU_PROPERTY_TYPE_0 Properties: x86 feature used: x86 x86 ISA used: x86-64-baseline GNU uses this section to leave notes for other tools to read. In this case, the assembler added that note, so we simply tell it not to by adding -Wa,-mx86-used-note=no. With that, we are now at 4320 bytes - almost small enough to fit into the Apollo guidance computer’s RAM. Our objdump now looks like it stores nothing except the instructions:\n❯ objdump -D a.out a.out: file format elf64-x86-64 Disassembly of section .text: 0000000000401000 \u0026lt;.text\u0026gt;: 401000:\t55 push %rbp 401001:\t48 89 e5 mov %rsp,%rbp 401004:\tb8 3c 00 00 00 mov $0x3c,%eax 401009:\t31 ff xor %edi,%edi 40100b:\t0f 05 syscall \u0026hellip;which is ridiculous. How can five lines of instructions result in a 4320 bytes binary??? The current output of readelf look as follows\n❯ readelf -a a.out ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2\u0026#39;s complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x401000 Start of program headers: 64 (bytes into file) Start of section headers: 4128 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 3 Size of section headers: 64 (bytes) Number of section headers: 3 Section header string table index: 2 Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000401000 00001000 000000000000000d 0000000000000000 AX 0 0 1 [ 2] .shstrtab STRTAB 0000000000000000 0000100d 0000000000000011 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), D (mbind), l (large), p (processor specific) There are no section groups in this file. Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000000e8 0x00000000000000e8 R 0x1000 LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000 0x000000000000000d 0x000000000000000d R E 0x1000 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 Section to Segment mapping: Segment Sections... 00 01 .text 02 There is no dynamic section in this file. There are no relocations in this file. No processor specific unwind information to decode No version information found in this file. The program header is the table that tells the OS loader how to map the file into memory segments when starting a program. Here, we see a LOAD of 232B (0xe8), which corresponds to (64B ELF header and three 56B program headers). There are also the instruction segments and the stack. The LOADs, however, have a Align requirement of 0x1000. To fulfil this, the linker had to put the .text after the paddings. We thus pass to GCC -Wl,–-nmagic to tell the linker not to have that assumption, nicely making our readelf output look as follows:\n❯ readelf -a a.out ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2\u0026#39;s complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x4000b0 Start of program headers: 64 (bytes into file) Start of section headers: 208 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 2 Size of section headers: 64 (bytes) Number of section headers: 3 Section header string table index: 2 Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 00000000004000b0 000000b0 000000000000000b 0000000000000000 AX 0 0 1 [ 2] .shstrtab STRTAB 0000000000000000 000000bb 0000000000000011 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), D (mbind), l (large), p (processor specific) There are no section groups in this file. Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x00000000000000b0 0x00000000004000b0 0x00000000004000b0 0x000000000000000b 0x000000000000000b R E 0x1 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 Section to Segment mapping: Segment Sections... 00 .text 01 There is no dynamic section in this file. There are no relocations in this file. No processor specific unwind information to decode No version information found in this file. There is only one LOAD now because we can map the combined data of ELF\u0026rsquo;s metadata and the .text section at the same time. Finally, we leapfrogged to 400 bytes! But can we do any better?\nI believe the answer is no. Our binary looks like this:\n+----------------------------------+ | ELF header | 64 B +----------------------------------+ | Program header: PT_LOAD | 56 B +----------------------------------+ | Program header: PT_GNU_STACK | 56 B +----------------------------------+ | .text section contents | 11 B +----------------------------------+ | .shstrtab section contents | 17 B | \u0026#34;\\0.shstrtab\\0.text\\0\u0026#34; | +----------------------------------+ | padding for section header | 4 B +----------------------------------+ | Section header [0]: NULL | 64 B +----------------------------------+ | Section header [1]: .text | 64 B +----------------------------------+ | Section header [2]: .shstrtab | 64 B +----------------------------------+ The ELF header is self-explanatory. PT_LOAD is needed to load the instructions, PT_GNU_STACK will always be produced by GCC. .shstrtab can\u0026rsquo;t be removed by GCC. The first section header entry is required by the System V ABI ELF specification to be reserved for the undefined section index, SHN_UNDEF, whose value is 0. In practice, this entry has type SHT_NULL, so tools display it as the NULL section. Tools like objcopy, however, will allow us to cut out some additional things, but that is out of today’s scope.\nStep Flags / change Size (bytes) Normal main gcc empty.c 15,816 Strip symbols -s 14,352 Freestanding -nostartfiles 13,632 No libc / static / no PIE -nostdlib -static -no-pie 8,704 Remove .comment section -fno-ident 8,616 Remove unwind info -fno-asynchronous-unwind-tables -fno-exceptions 4,400 Remove GNU property note -Wa,-mx86-used-note=no 4,320 Reduce alignment -Wl,--nmagic / -Wl,-n 400 So there we have it - the final code used:\n// gcc -Wl,--nmagic -Wa,-mx86-used-note=no -static -nostdlib -no-pie -s -fno-ident -fno-exceptions -fno-asynchronous-unwind-tables empty.c extern \u0026#34;C\u0026#34; __attribute__((noreturn)) void _start() { __asm__ volatile( \u0026#34;mov $60, %%eax\\n\u0026#34; // SYS_exit \u0026#34;xor %%edi, %%edi\\n\u0026#34; // exit status 0 \u0026#34;syscall\\n\u0026#34; :: : \u0026#34;rax\u0026#34;, \u0026#34;rdi\u0026#34;); __builtin_unreachable(); } ","permalink":"https://blog.weineng.me/posts/smallest_c/","summary":"\u003cp\u003eI thought of a cute problem: what is the smallest (size) \u003ccode\u003e./a.out\u003c/code\u003e binary I can create?\u003c/p\u003e\n\u003cp\u003eHere are some rules the program should follow:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003e./a.out\u003c/code\u003e must run successfully.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e$?\u003c/code\u003e must deterministically be \u003ccode\u003e0\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eThe binary must be produced by GCC only; no post-processing with \u003ccode\u003eobjcopy\u003c/code\u003e, hex editors, or manual patching.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWe begin with the simplest program possible:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// compiled with gcc empty.c\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis gives us a file size of 15816 bytes (from \u003ccode\u003estat\u003c/code\u003e). Not too shabby, but we will need four of the RAM used in the \u003ca href=\"https://en.wikipedia.org/wiki/Apollo_Guidance_Computer\"\u003eApollo guidance computer\u003c/a\u003e to fit our binary that does nothing.\u003c/p\u003e","title":"The smallest C binary"}]