AI 资讯
🚀 Mastering OOP for Interviews : Understanding Abstraction from First Principles (C++)
Series: Master OOP for Software Engineering Interviews Introduction Ask ten beginner developers: "What is abstraction?" Most answers sound like this: "Abstraction is the process of hiding implementation details and showing only essential information." Technically, that's correct. But if I ask the next question: "Why was abstraction invented?" or "Can you explain abstraction using an Inventory Management System?" or "How is abstraction different from encapsulation?" many candidates struggle. That's because they memorized the definition instead of understanding the idea behind it. In this article, we'll learn abstraction the way experienced software engineers think about it—not by memorizing definitions, but by understanding why it exists, what problem it solves, and how it appears in every modern software system. 🎯 Learning Goals After reading this article, you should be able to: Explain abstraction without memorizing a textbook definition. Understand why abstraction exists. Identify abstraction in everyday life. Recognize abstraction in software systems. Confidently answer beginner interview questions. Build a strong mental model that makes future OOP concepts easier. Before We Learn Abstraction... Let's ask an important question. Why do programming languages even provide OOP? Imagine writing software for an e-commerce company. The system contains: Products Customers Orders Warehouses Payments Delivery Partners Notifications Discounts Reviews Thousands of features. If every developer had to understand every implementation detail before writing code, software development would become impossible. We need a way to reduce complexity. That solution is called abstraction. The Problem Abstraction Solves Imagine buying a new car. You sit inside. You: Press the accelerator. Turn the steering wheel. Shift gears. Press the brake. Simple. But underneath the hood, hundreds of complex operations happen every second. The engine burns fuel. The pistons move. The gearbox changes tor
AI 资讯
Python vs C++ for Embedded Systems: When to Use Each
When you first step into the world of embedded systems, one of the earliest and most consequential decisions you will face is choosing a programming language. Two names come up more than any others: Python and C++. Both are powerful, both have passionate communities, and both are genuinely useful — but for very different reasons and in very different contexts. This article is not about declaring a winner. It is about understanding why each language exists in this space, what trade-offs you are actually making, and how to make a confident, informed decision for your next project. Understanding the Fundamental Difference Before comparing features, it helps to understand why these two languages feel so different at a deeper level. C++ is a compiled, statically-typed, systems-level language . When you write C++, you are writing code that gets translated directly into machine instructions. You manage memory manually. You control exactly when objects are created and destroyed. The hardware does precisely what you tell it to, nothing more and nothing less. This directness is both its superpower and its source of complexity. Python, by contrast, is an interpreted, dynamically-typed, high-level language . A Python runtime sits between your code and the hardware, managing memory automatically through garbage collection, resolving types at runtime, and handling a lot of bookkeeping so you don't have to. This makes Python wonderfully expressive and fast to write, but it introduces overhead that matters enormously on constrained hardware. The mental model to hold onto is this: C++ gives you control, Python gives you speed of development . Both are valuable. The question is which one your project needs more. Where C++ Shines in Embedded Systems 1. Bare-Metal and Resource-Constrained Environments If you are programming a microcontroller like an STM32, an AVR ATmega, or an ESP32 running its native SDK, C++ is almost always your primary language. These devices often have kilobytes —
AI 资讯
MCP Explained: How It's Different from Traditional APIs
Imagine you are planning a surprise birthday party. You need invitations, food, decorations, and a cake. You call different places to get these things. You tell each one exactly what you need. "I need 20 red balloons." "I need a chocolate cake for 10 people." This is how many computer programs talk to each other. They use something called an API (Application Programming Interface). An API is like a menu. You pick what you want. You get exactly that. It works well for simple tasks. But what if your party plans change? What if you decide on a theme mid-conversation? Traditional APIs can feel a bit rigid then. They don't always remember your past requests. They don't understand the bigger picture. Now, imagine talking to a super-smart party planner. You start by saying, "I'm planning a party." The planner asks, "For how many people?" You say, "About 20." Then you mention, "It's for a birthday." The planner instantly suggests a cake size. It recommends decorations based on your earlier answers. This smart planner remembers everything you said. It understands your overall goal. It uses something like MCP (Model Context Protocol). MCP is a new way for computers to talk. It's like having a real conversation. It's much smarter than a simple menu order. You will soon understand why this difference is a game-changer. Traditional APIs: The Fixed Menu Approach Let's start with what you might already know. Many apps you use every day rely on APIs. An API is like a waiter in a restaurant. You look at the menu. You tell the waiter your exact order. "I want a cheeseburger with fries." The waiter takes your order to the kitchen. The kitchen prepares only that specific meal. Then the waiter brings it back to you. This is how most apps work together. One app sends a very specific request. It asks for a certain piece of information or to perform a specific action. The other app performs that task. It sends back a very specific response. Think of ordering from an online store. You click
AI 资讯
Capturing Attributes in Execution Calculations
Note: If you're new to execution calculations, I'd recommend starting with my previous post which covers them in detail, then coming back here. What is attribute capturing and why would you use it Attribute capturing refers to directly exposing certain attributes to your execution calculation from attribute sets, to be used in execution calculations. Capturing attributes lets you handle things like damage in a more complex way. So for example, you could have advantages and weaknesses against certain damage types, or an attribute like armor that should reduce incoming damage. This is just two use cases, but once you learn how to do it, you should naturally be able to see in what other ways they can be used. In this example, I will show how to get attribute magnitude from captured attributes and using them in calculations, which is the most common way of using captured attributes. Capturing attributes Defining the struct, declaring and defining attribute capture definitions In this post, I will show how to use a static struct to access all your captured attributes, as a good performance-aware solution. You are also free to choose not to use the struct. For the struct approach, the first step is to go to the .cpp of the execution calculation, and define your struct there. In the struct, we first use the DECLARE_ATTRIBUTE_CAPTUREDEF() macro, passing in the name of your attribute. After that, we make the constructor of the struct, and in the constructor we use the DEFINE_ATTRIBUTE_CAPTUREDEF() macro, passing in the attribute set that has the attribute, the attribute from it, whether the attribute from the Target or Source should be used (the Target is the ASC the ExecCalc is outputting its result to, and the Source is the ASC that called it), and a bool for if the captured attribute should be snapshotted (if the attribute should be frozen at ExecCalc GE application (snapshotted) or if the value should be read at execution time). In my case, I will show 2 examples of non-
AI 资讯
A small C++ library for sending structured commands and telemetry between devices — no schema files, just add your parameters and serialize
If you've ever tried to build a simple command/telemetry protocol between a PC and a fleet of SDR receivers, sensors, or embedded devices, you know the usual options aren't great: Roll your own binary format — fast, but you end up writing and maintaining custom serialization code for every device type, and debugging mismatched structs across machines is painful. Protobuf / FlatBuffers — robust, but require you to define your message layout in a schema file upfront, run a code generator as part of your build, and commit to a fixed structure. Adding a new device type or a new parameter means editing the schema, regenerating, recompiling everything. JSON over the wire — easy to debug, but heavy for anything real-time or bandwidth-constrained. I ran into this while working on a multi-SDR receiver system and ended up writing MessageFrame — a small C++17 library that lets you build structured messages dynamically, without any schema files or code generation. The basic idea Instead of defining a struct for each device type, you address each parameter with two strings — a device name and a parameter name — and the library handles the rest: // One message, multiple devices, assembled at runtime msgframe :: MessageFrame msg ( MSG_TELEMETRY , TYPE_PERIODIC , src = 1 , tgt = 2 ); msg . add ( "sdr_1" , "rx_gain" , VALUE ( 30.0 )); msg . add ( "sdr_1" , "center_freq" , VALUE ( 915'000'000.0 )); msg . add ( "sdr_1" , "sample_rate" , VALUE ( 2'000'000.0 )); msg . add ( "sdr_2" , "rx_gain" , VALUE ( 25.0 )); msg . add ( "sdr_2" , "lock_status" , VALUE ( true )); msg . add ( "psu_1" , "voltage" , VALUE ( 12.04 )); msg . add ( "psu_1" , "temp_c" , VALUE ( 47.3 )); // Attach raw IQ data alongside the parameters std :: vector < uint8_t > iq_buffer = { 0x01 , 0x02 , 0x03 , 0x04 }; msg . add_attachment ( "raw_iq" , std :: move ( iq_buffer )); // Serialize into a buffer, send over whatever transport you use std :: vector < uint8_t > out ; msg . serialize ( out ); send_udp ( out . data (),
AI 资讯
Three Small Shell Scripts That Make HackerRank/DevSkiller C++ Take-Homes Way Less Painful
If you've ever done a timed C++ coding assessment on a platform like HackerRank or DevSkiller, you know the friction isn't really the algorithm — it's the loop . Download a zip with a weird filename, unzip it, hunt for the project root, configure CMake, build, run GTest, fix one failing test, repeat... and somewhere in there you've burned ten minutes of your one-hour window just fighting the harness instead of writing code. These platforms' in-browser editors are fine for quick problems, but for anything involving multiple files (headers, sources, a real test suite), I'd rather work in my own terminal and editor. The catch is that you still have to get the project out of the browser sandbox, build it locally with the exact same toolchain (CMake + GTest), and then package it back up in a way the grader will accept. So I wrote three small bash scripts to remove that friction entirely. Sharing them here in case they save someone else the same ten minutes. The workflow Download the project archive from the platform (zip or tar.gz, filename is whatever the platform gives you — often randomized) Extract it — script 1 handles this regardless of filename or archive type Iterate — script 2 configures CMake once, then repeatedly builds and runs GTest, optionally watching for file changes Package — script 3 strips build artifacts and any local helper scripts, then zips it back up under a name that won't collide with the original download, ready to re-upload Script 1: extract_and_setup.sh Most of these platforms hand you an archive with an unpredictable filename. This script extracts whatever you point it at ( .tar , .tgz , .tar.gz , or .zip ), figures out which directory it unpacked to by diffing the folder listing before and after, and drops the build script into it automatically. #!/usr/bin/env bash # extract_and_setup.sh # Extracts $fname (tar, tgz, tar.gz, or zip) into the CURRENT folder, # then copies run_build.sh into the directory that was created. # # Usage: # ./extrac
AI 资讯
The Introduction
Operating system, a thing that everybody uses but no one talks about. While reading Operating Systems: Three Easy Pieces (OSTEP), my background in C and C++ fueled a growing fascination with memory allocation, virtualization, scheduling, and the intricate mechanics of operating systems. This would be a series of article, the number i am not sure, it will be the amount of content that someone might comfortably read in a 10 min Article. Keeping each piece to a solid 10-minute read is the perfect sweet spot for a developer to read over a cup of coffee. It gives you enough runway to explain a core concept, show the math, and link a practical C/C++ experiment without making their eyes glaze over. Why this Article ? We are often warned against “reinventing the wheel.” However, I firmly believe that building and optimizing modern software is impossible without a fundamental grasp of virtualization, memory allocation, and concurrency. Consider Docker: it functions almost entirely on OS-level virtualization features like Namespaces, cgroups, and isolated filesystems. Similarly, the highly optimized Memory Manager in PostgreSQL only works because it leverages the robust memory management systems already written into the OS kernel. This article aims to bring the core concepts of OSTEP to life through practical experimentation. By accompanying the theory with an open-source repository, my goal is to provide a clear, interactive learning experience that demystifies operating systems. I am not an operating system guru or a Principal Engineer with years of experience, but I hope to become one someday (assuming AI doesn’t replace me first… HeHe ). What I can do is dive in, explore, and try to understand these concepts by actually building things. Because of that, my goal here is to present the findings and experiments I explore rather than giving strong opinions — I’ll leave the comment section for those! Any support, feedback, or contributions from the community will be incredibly
AI 资讯
How to optimize IO in C++
Preface Link to the repository containing all examples Back when I was at my first class of Data Structures and Algorithms, I started to solve competitive programming questions in judges like CodeForces, I didn't know why my code was slower if my solution was efficient (at least in theory), my professor explain to the class the reason why our programs were slow, because of I/O is an expensive task for computers. To start working with the examples in this article just clone the repository and play with it as long the article explains how to run the code. git clone https://github.com/MiztonCodes/OptimizeIO.git Why std::cout and std::cin are slow? In C++ by default, both std::cin and std::cout streams are synchronized with standard C scanf and printf streams and at the same time every single call to std::cin flushes the std::cout buffer, because std::cin before reading input, wants to output any pending prompt to the user, which can cause performance issues because reading and writing are expensive tasks due to the need to make calls to the operating system, the solution is to disable all of this synchronizations and manually flushing the output buffer whenever it is needed. Note: For convenience in all examples I'll use #include<bits/stdc++.h> header to simplify the imports, this header includes everything we'll need and this header only works on GCC compiler. What is input and output? When you run your program, it is like an isolated box inside your computer, ready to do some work, the input process involves moving raw data from an external source like a keyboard, a file or even the network to your program (reading) , the output process involves moving data from your program to an external destination like a monitor, the console, a file, the network or even an external device (writing) , for both processes your program does not read or write directly to the input and output sources, because each source has its own way to transfer data, C++ provides a uniform interfac
AI 资讯
Quill vs spdlog: Which C++ Logger Is Better for Low-Latency Applications?
Logging has a habit of ending up in the places you care about most. It starts as a few lines for visibility. Then those lines appear in request handling, market-data processing, matching loops, telemetry pipelines, and other code where predictable latency matters. At that point, a log statement is no longer just observability. It is work running on the same thread you are trying to keep fast. A line like this can look harmless: LOG_INFO ( logger , "order_id={} price={}" , order_id , price ); The important question is what happens before the caller continues . Does it evaluate expensive arguments? Format text? Copy buffers? Allocate? Contend with other producer threads? Wait for queue space? For many applications, those costs are acceptable. For latency-sensitive systems, they are part of the latency budget . spdlog is one of the best-known C++ logging libraries and a strong general-purpose choice. It is mature, easy to use, and has a broad feature set. Quill was designed for a narrower problem: How little work can a C++ logger leave on the caller thread while still producing rich, human-readable logs? That is the lens for this comparison. The interesting difference is not which library has more features. It is where each library chooses to spend work. At a Glance Area spdlog async Quill User-message formatting Producer thread Backend thread Producer handoff Shared thread-pool queue Per-thread SPSC queue Arguments for runtime-disabled levels Evaluated if the level was not compiled out Skipped by the macro-level runtime check Native synchronous mode Yes No Backend workers Configurable thread pool Single backend worker Primary focus General-purpose flexibility Low producer-side latency These differences do not make one library universally better. They make each library better suited to different workloads. Async Logging Is Not One Design "Async logging" often means "file I/O happens on another thread." That is useful, but it is not enough to describe the cost paid by t
开发者
Pointers and Tuning and Loops! Oh My!
Introduction While all code should be efficient, code for library-like components, especially involving loops, should be as efficient as possible since such code is often widely used. In my A Simple Dynamic Array for C , I included the source code for a function to clean-up a dynamic array: void array_cleanup ( array_t * array , array_free_fn_t free_fn ) { if ( array == NULL ) return ; if ( free_fn != NULL ) { char * element = array -> elements ; for ( size_t i = 0 ; i < array -> len ; ++ i ) { ( * free_fn )( element ); element += array -> esize ; } } free ( array -> elements ); array_init ( array , array -> esize ); } While this code is correct and good enough for pedagogical purposes, it’s not optimal. Before I tell you why, see if you can figure out why. Hint: it has to do with the use of both the array pointer and the function call in the loop. For those who might not get the reference in this article’s title, it’s a play on a line from The Wizard of Oz . The original line was “Lions and tigers and bears! Oh, my!” Loop Refresher In C (and languages inspired by C including C++, C#, Go, and Java), for loop conditions are reevaluated on every loop iteration. For example, in: for ( int i = 0 ; i < n ; ++ i ) the condition i < n is evaluated on every iteration. That means if n is changed within the loop, it could terminate either earlier or later than it was initially supposed to — and this is well-defined behavior. This is in contrast to some older languages like Fortran and Pascal as well as some newer languages like Rust where the loop’s termination value is evaluated once just prior to the start of the loop. For example, in Pascal: for i := 1 to n do is actually treated as if it were this: limit := n ; for i := 1 to limit do Note that if the loop condition is a more complicated expression, such as calling a function: for ( int i = 0 ; i < f ( x ); ++ i ) then the function will be called on every loop iteration. Except if the function is marked as unsequenced in C
AI 资讯
llama-bench skipped FA on capable GPUs — b9437 corrects it
What flipped in b9437 Build b9437 , published on May 30, 2026 at 20:56 UTC , ships two targeted default-value corrections to llama-bench . Flash attention ( -fa ) shifts from a hard-coded off to auto ( LLAMA_FLASH_ATTN_TYPE_AUTO ), and the GPU-layer count ( -ngl ) changes from the legacy sentinel 99 to -1 . Both values now match what llama-server and llama-cli already used — the bench tool was simply never updated to track them until this build. Quick Answer: Before b9437 (published May 30, 2026) , llama-bench hard-coded -fa off , silently skipping flash attention even on CUDA, Metal, and Vulkan hardware. Build b9437 sets the default to -fa auto and -ngl -1 , matching llama-server and llama-cli . Any pre-b9437 baseline on FA-capable hardware needs a flag-matched re-run to remain valid. PR #23714 , reviewed and merged by maintainers JohannesGaessler and pwilkin, adds the same -fa auto|off|on tri-state flag to llama-bench that the rest of the toolchain already supported. With LLAMA_FLASH_ATTN_TYPE_AUTO as the new default, flash attention activates automatically when the runtime detects a capable backend (CUDA, Metal, Vulkan); on CPU-only hosts it stays off with no error and no output change. Parameter Before b9437 After b9437 Behavioral impact -fa off (hard-coded) auto ( LLAMA_FLASH_ATTN_TYPE_AUTO ) GPU-capable hosts bench with FA active by default; pre/post comparisons require explicit flag-matching -ngl 99 (offload-all sentinel) -1 (runtime decides) CPU-only builds no longer attempt full GPU offload; eliminates spurious CUDA errors when no GPU is present The following verified script (executed successfully, exit 0) demonstrates the behavioral gap in concrete terms — on a capable GPU, the pre-b9437 defaults schedule zero FA rows while b9437 defaults schedule one: def old_llama_bench ( device ): # Before b9437, the default bench matrix used FA=0, so FA rows were skipped. return [{ " device " : device [ " name " ], " ngl " : 0 , " fa " : 0 }] def b9437_llama_bench ( de
AI 资讯
C++ and Microarchitecture Nuances
C++ source code is written in order. That does not mean the processor executes it in order. This is the first correction. It is also the one many performance discussions manage to avoid. Modern high-performance cores use out-of-order execution . They accept a sequential instruction stream, break it into internal operations, rename registers, place work into scheduling structures, execute ready operations early, and then retire the results in program order. The machine preserves the visible behavior of sequential execution. Internally, it is not taking attendance line by line. For ordinary software, this is mostly invisible. For C++ intended to run in tens of nanoseconds, it is not invisible. At that scale, performance is not just about the number of instructions. It is about whether those instructions can be scheduled in parallel or whether the program quietly built a dependency chain and then acted surprised. The processor is a dependency scheduler Out-of-order execution exists because in-order pipelines waste time. If an older instruction stalls, an in-order processor must often wait even if later instructions are independent and ready. That is a poor use of hardware. The chip has execution units available. The instruction stream has more work. Dynamic scheduling fixes part of this problem. The processor tracks which operations have their inputs ready. When an operation is ready and an execution unit is available, it can issue. Older operations may still be waiting. Later operations may run first. The final architectural state is still committed in order, so the program behaves correctly. Tomasulo’s algorithm is the classic model for this idea. It used reservation stations and register renaming to allow instructions to execute when their operands became available rather than strictly when they appeared in the original program (Tomasulo, 1967). Later superscalar processors extended the same general approach with speculation and reorder buffers, but the central idea
AI 资讯
Canadian pension giant joins race to fund India’s AI-fueled data center boom
The Canadian pension giant will acquire an 8.2% stake in CtrlS, a tech giant that operates more than 15 data centers across India.
AI 资讯
Because in a Life-Threatening Situation, Every Millisecond Counts
Removing expf() from a fire detector: one header, 1.95x faster, zero accuracy loss A smoke detector is not a demo project. When it fires, someone either evacuates in time or doesn't. The firmware running on that microcontroller has one job, and it needs to do it without hesitation, without bloat, and without dependencies that can fail in unexpected ways. Last May 28th I published a bare-metal fire detection system built with Hasaki 刃先 — a neural network trainer that exports standalone C headers with no runtime, no Python, no TensorFlow. The model is a 12-8-4-1 MLP trained on 28,596 sensor readings. It fits in 3.8 kB of Flash and achieves 99.93% accuracy on held-out data, with a single missed fire event out of 3,599. But there was something in that header that bothered me. static inline float sigmoid ( float x ) { return 1 . 0 f / ( 1 . 0 f + expf ( - x )); } expf() . Right there in a life-safety application. On a microcontroller that may not have a hardware FPU. The problem with expf() on bare metal On processors with a hardware FPU — like the ESP32-C3 — expf() is fast. But the moment you deploy to an ATmega328P, an ATtiny85, or any Cortex-M0 target, that call becomes software floating-point. The CPU has to simulate the operation in firmware, cycle by cycle. It works. But it carries hidden cost: unpredictable latency, dependency on math.h , and a transcendental function sitting in the critical path of every single inference. For a smoke detector running at 1 Hz this might seem irrelevant. But inference latency compounds with sensor reads, normalization, and communication overhead. And more importantly — if you're deploying to a truly constrained target, expf() might be the difference between fitting in Flash or not. The fix: one header from kigu-quant kigu-quant(comming soon) is a new tool in the Rosito Bench ecosystem. It generates ready-to-include C headers for evaluating mathematical functions on microcontrollers — no FPU, no libm, no dependencies. One command: k
开发者
Looking to connect with fellow C++ learners and developers
Hi everyone 👋 I'm currently learning C++ and looking to connect with other people who enjoy programming. I'm interested in improving my coding skills, building small projects, and learning from more experienced developers. If you're also learning C++ or are willing to share advice with a beginner, I'd be happy to chat and learn together. Happy coding! 🚀
AI 资讯
Why I built a native libmpv IPTV player for Windows — an HDR tone-mapping deep-dive
Up front, so there's no confusion: the app I'm describing (Nightmare TV) is a player only . You bring your own M3U / Xtream Codes playlist — it ships with no channels and no content. This post is about the playback engineering, not about where streams come from. Think "VLC for IPTV," not a content service. The problem that started it I watch a lot of live content on my PC — sports, mostly. And every IPTV player I tried on Windows fell into one of two buckets: An Android app running in an emulator. TiviMate and the good mobile players are Android-only, so on a desktop you end up in an emulator or a VM. Input lag, no real HDR path, fans spinning. A thin ExoPlayer / libVLC wrapper. These run natively, but most of them treat HDR as "pass the HDR10 metadata to the display and hope." On an SDR panel — or even a lot of HDR panels — bright skies in a football match blow out to a flat white blob, and 4K HEVC with a DTS track stutters because the decode path isn't doing what you think it is. I wanted the thing that didn't exist: a native Windows player with a reference-grade video path. So I built it on libmpv — the same playback core mpv uses — with a Flutter desktop shell on top for the UI. This post is the part I actually find interesting: the HDR tone-mapping pipeline. Why HDR "just passing through" isn't enough HDR10 content is mastered in the PQ (ST.2084) transfer function against a mastering display — often 1000 nits, sometimes 4000. Your screen is whatever it is: a 350-nit SDR laptop, a 600-nit "HDR400" monitor, an 800-nit OLED. If you map PQ straight to the panel, everything above the panel's peak just clips — all the highlight detail collapses to maximum white. Tone-mapping is the process of intelligently compressing the mastering range into the display range so you keep highlight detail instead of clipping it. The naive version (a fixed curve, or clipping) is what most wrapper players ship. The good version adapts to both the content and the display. The pipeline H
AI 资讯
The Two Things That Bit Me In Emscripten
While building a web application using React, TypeScript, C++, Emscripten, and Raylib, I ran into two linker-related issues that took far longer to diagnose than they should have. This is a short article on two problems that I have faced, I am sharing this so that developers who are exploring Web Assembly using Emscripten can easily avoid these issues as I'll also cover the workarounds that solved them for me. I'll start with the major one. Link-Time Optimization and EM_JS The problem appears when Link Time Optimization (-flto) is enabled and an EM_JS(ret, name, params, ...) macro function is invoked in one translation unit but called from another. You'll find that the linker complains that the function symbol you're trying to call is undefined. The EM_JS macro defines a C interface to call the JS function. So if you have your EM_JS macros in a js_layer.cpp source file, then you need to wrap the macro invocation as extern "C" { EM_JS ( void , add , ( int a , int b ), { //your JS code; }); } The same applies to the function declaration in js_layer.hpp file. I'll briefly go through the three workarounds or 'solutions' before bringing up the last quirk. The Three Workarounds If performance isn't of any concern Well the first one is obvious, just don't use -flto in your release builds. The downside is that your binaries (the .data and .wasm compiler outputs in this case) will be larger. Without LTO, the linker has fewer opportunities for whole-program optimization and dead-code elimination, which can increase the size of the generated .wasm and potentially reduce performance. Write a wrapper You can simply have a wrapper function in the same translation unit which calls the C function interfacing with the JS function, add() if you take the above example. The wrapper can simply be: // In js_layer.hpp void add_wrapper ( int , int ); // In js_layer.cpp void add_wrapper ( int a , int b ){ add ( a , b ); } Now you can call add_wrapper() from any source file just by including