惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
Attack and Defense Labs
Attack and Defense Labs
WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Spread Privacy
Spread Privacy
AI
AI
宝玉的分享
宝玉的分享
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
C
Cyber Attacks, Cyber Crime and Cyber Security
爱范儿
爱范儿
Help Net Security
Help Net Security
V
Visual Studio Blog
大猫的无限游戏
大猫的无限游戏
Forbes - Security
Forbes - Security
P
Privacy & Cybersecurity Law Blog
Project Zero
Project Zero
IT之家
IT之家
Hugging Face - Blog
Hugging Face - Blog
博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
有赞技术团队
有赞技术团队
T
Troy Hunt's Blog
美团技术团队
T
Threatpost
K
Kaspersky official blog
V
V2EX
Scott Helme
Scott Helme
Vercel News
Vercel News
T
The Blog of Author Tim Ferriss
T
Tailwind CSS Blog
V
Vulnerabilities – Threatpost
Last Week in AI
Last Week in AI
PCI Perspectives
PCI Perspectives
Google Online Security Blog
Google Online Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
I
InfoQ
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
酷 壳 – CoolShell
酷 壳 – CoolShell
GbyAI
GbyAI
L
LINUX DO - 最新话题
T
The Exploit Database - CXSecurity.com
L
LangChain Blog
S
Security @ Cisco Blogs
The Last Watchdog
The Last Watchdog
H
Hacker News: Front Page
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
TaoSecurity Blog
TaoSecurity Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Your Struct is Wasting Memory and You Don't Know It
Dharrsan Ama · 2026-04-25 · via DEV Community

We write structs by listing fields in whatever order feels readable. Name, then age, then score. It compiles. It runs. The compiler silently bloats it, misaligns it, or both, and you ship it without ever checking.

Here are three structs holding the exact same six fields:

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

struct Good {
    double balance;
    uint64_t transaction_id;
    int32_t account_type;
    int16_t region_code;
    char status;
    char currency[4];
};

struct Bad {
    char status;
    double balance;
    int16_t region_code;
    uint64_t transaction_id;
    char currency[4];
    int32_t account_type;
};

struct __attribute__((packed)) PackedBad {
    char status;
    double balance;
    int16_t region_code;
    uint64_t transaction_id;
    char currency[4];
    int32_t account_type;
};

int main() {
    printf("Good:      %zu bytes\n", sizeof(struct Good));
    printf("Bad:       %zu bytes\n", sizeof(struct Bad));
    printf("PackedBad: %zu bytes\n", sizeof(struct PackedBad));
    return 0;
}

Enter fullscreen mode Exit fullscreen mode

Good:      32 bytes
Bad:       40 bytes
PackedBad: 27 bytes

Enter fullscreen mode Exit fullscreen mode

Same fields. 27, 32, and 40 bytes. The difference is not the data. It is the order and whether you let the compiler do its job.


What Happens When You Read One Byte

Before touching any struct, you need to understand how the CPU actually talks to RAM. There are three buses connecting them.

The address bus carries the memory address the CPU wants to read. It is 48 to 52 physical wires on a modern system. The CPU puts a number on these wires and RAM listens.

The data bus carries the actual bytes back. It is 64 bits wide, so 8 bytes travel in parallel per transfer. But the CPU does not stop at 8 bytes. It keeps bursting transfers across the data bus until it has filled a full cache line.

A cache line is 64 bytes. That is the only unit of communication between RAM and your L1/L2 cache. The CPU never fetches 1 byte. It never fetches 8 bytes. It always fetches 64 bytes. When you read a single char, the CPU puts that char's address on the address bus, pulls the entire 64-byte block containing it across the data bus, stores it in cache, and then gives you your one byte out of it.

Every cache line starts at an address that is a multiple of 64. Cache line 0 covers 0x0000 to 0x003F (0 to 63). Cache line 1 covers 0x0040 to 0x007F (64 to 127). Cache line 2 covers 0x0080 to 0x00BF (128 to 191). The boundaries are fixed and always at multiples of 64.

This is the rule everything else in this post follows.


Natural Alignment and Why It Matters

Every data type has an alignment requirement equal to its own size. A double (8 bytes) must start at an address divisible by 8. A uint32_t (4 bytes) must start at an address divisible by 4. A char (1 byte) can go anywhere.

When a field sits at a naturally aligned address the CPU reads it in one bus transaction. It fits cleanly inside one cache line fetch.

When a field is misaligned it can straddle a cache line boundary. Say a double starts at 0x003C (60). It is 8 bytes, so it occupies 0x003C to 0x0043 (60 to 67). Cache line 0 ends at 0x003F (63). Cache line 1 starts at 0x0040 (64). Your double is split across both. The CPU issues an address request for cache line 0, waits for the data bus to deliver, then issues a second address request for cache line 1, waits again, and stitches both halves together in hardware. Two full round trips to memory for one field read.

Now think about why address mod data_size == 0 prevents this. Cache line boundaries sit at multiples of 64. A naturally aligned double sits at a multiple of 8. The worst case is a double at 0x0038 (56), occupying bytes 56 to 63. It ends exactly at the cache line boundary, never crossing it. This works because 64 is itself a multiple of 8. A field aligned to its own size mathematically cannot straddle a boundary that is also a multiple of that same size. So address mod data_size == 0 is not a style convention. It is the condition that guarantees your field lives inside exactly one cache line, fetched in exactly one bus transaction, with no possibility of being split.

The compiler inserts padding between fields to maintain this guarantee. Bad field ordering forces it to insert a lot of padding. And packed removes all of it.


Good : 32 bytes, nothing wasted

0x0000      0x0008      0x0010 0x0014 0x0016 0x0017  0x001B  0x001F
(0)         (8)         (16)   (20)   (22)   (23)    (27)    (31)
|-----------|-----------|------|----|--|------|--------|
balance     trans_id    acct   reg  st currency  pad

Enter fullscreen mode Exit fullscreen mode

balance at 0x0000 (0). 0 mod 8 = 0. Aligned.
transaction_id at 0x0008 (8). 8 mod 8 = 0. Aligned.
account_type at 0x0010 (16). 16 mod 4 = 0. Aligned.
region_code at 0x0014 (20). 20 mod 2 = 0. Aligned.
status at 0x0016 (22). Char, goes anywhere.
currency at 0x0017 (23). Char array, goes anywhere.

Every field starts exactly where the previous one ended. Zero internal padding.

The 5 bytes at the end are tail padding. In an array the second element must start at an address divisible by 8, the largest field alignment. Without tail padding the second element begins at 0x001B (27) and its balance field lands there too. 27 mod 8 = 3. Misaligned. So the compiler rounds 27 up to 32. The second element starts at 0x0020 (32). 32 mod 8 = 0. Clean.

Zero bytes wasted internally. The tail padding is structural and unavoidable.


Bad : 40 bytes, 13 bytes of dead space

0x0000 0x0001    0x0008      0x0010 0x0012    0x0018      0x0020 0x0024 0x0028
(0)    (1)       (8)         (16)   (18)      (24)        (32)   (36)   (40)
|------|---------|-----------|------|---------|-----------|------|----|
st     7B pad    balance     reg    6B pad    trans_id    curr   acct

Enter fullscreen mode Exit fullscreen mode

status at 0x0000 (0), one byte. The next field is balance, a double that needs a multiple of 8. After byte 1, the nearest multiple of 8 is 0x0008 (8). The compiler inserts 7 bytes of padding between them that store nothing and do nothing.

Then region_code lands at 0x0010 (16), two bytes, ending at 0x0011 (17). The field after it is transaction_id, which needs a multiple of 8. The nearest is 0x0018 (24). Six more bytes gone.

13 bytes wasted purely from putting char status first. In an array of a million of these structs that is 13MB of RAM holding nothing. The struct is 25% larger than it needs to be, meaning fewer elements fit per cache line and more trips to RAM on every access pattern.


PackedBad : 27 bytes, zero padding, four misaligned fields

0x0000 0x0001    0x0009 0x000B      0x0013  0x0017 0x001B
(0)    (1)       (9)    (11)        (19)    (23)   (27)
|------|---------|------|-----------|--------|------|
st     balance   reg    trans_id    curr    acct

Enter fullscreen mode Exit fullscreen mode

__attribute__((packed)) removes all padding. Fields sit back to back. 27 bytes. But look at where each field actually lands:

balance at 0x0001 (1). 1 mod 8 = 1. Not 0. Misaligned.
region_code at 0x0009 (9). 9 mod 2 = 1. Not 0. Misaligned.
transaction_id at 0x000B (11). 11 mod 8 = 3. Not 0. Misaligned.
account_type at 0x0017 (23). 23 mod 4 = 3. Not 0. Misaligned.

Four fields, zero aligned. In an array, whether a given element straddles a cache line boundary depends on its index. You can check with:

(index x struct_size) mod 64 + struct_size > 64

Enter fullscreen mode Exit fullscreen mode

For element 2 of PackedBad: (2 x 27) mod 64 + 27 = 54 + 27 = 81. Since 81 > 64, element 2 straddles. Its bytes run from 0x0036 (54) to 0x0054 (84), crossing the cache line boundary at 0x0040 (64). The CPU issues an address request for cache line 0 (0x0000 to 0x003F), waits for the data bus, issues a request for cache line 1 (0x0040 to 0x007F), waits again, and stitches both halves. Two full round trips for one struct read. You saved 13 bytes on paper and doubled your memory traffic in practice.


Tearing

The straddle is slow. In single-threaded code it is just slower. In multithreaded code it is also wrong.

The CPU guarantees a memory access is atomic, meaning indivisible and instantaneous from every other thread's perspective, only when:

address mod data_size == 0

Enter fullscreen mode Exit fullscreen mode

That condition guarantees the field sits inside one cache line and the CPU fetches it in one bus transaction. One transaction means no window for another thread to slip in.

When balance sits at 0x0001 (1) in PackedBad, 1 mod 8 = 1. The condition fails. The CPU fetches the first portion of balance in one bus transaction, then the second portion in a separate bus transaction. There is a real time gap between them.

If another thread writes to that same balance field inside that gap, the reading thread gets the first half from before the write and the second half from after it. A value assembled from two different points in time. A number that was never logically written anywhere in your program.

No segfault. No assertion. No log line. The field silently reads as garbage. In a monitoring system this corrupts your metrics. In a financial system this is a balance that never existed reaching your business logic.

Good and Bad are both padded by the compiler so every field satisfies address mod data_size == 0. Tearing cannot happen. PackedBad has four fields that fail this condition in every element.


All Three Side by Side

Struct Size Internal padding Misaligned fields
Good 32 bytes 0 bytes 0
Bad 40 bytes 13 bytes 0
PackedBad 27 bytes 0 bytes 4

Bad pays in memory. PackedBad pays in correctness. Good pays nothing.


The Fix Is Just Field Order

Order fields from largest alignment requirement to smallest:

struct Good {
    double balance;          // 8 bytes
    uint64_t transaction_id; // 8 bytes
    int32_t account_type;    // 4 bytes
    int16_t region_code;     // 2 bytes
    char status;             // 1 byte
    char currency[4];        // 1 byte alignment
};

Enter fullscreen mode Exit fullscreen mode

The compiler has nothing to pad because each field naturally follows the previous one without any gap. No attributes, no pragmas. Just ordering.

Verify with sizeof. Inspect individual field positions with __builtin_offsetof(struct Foo, field) when something looks off.


When packed Is Actually Correct

__attribute__((packed)) has one valid use: serializing data onto a network socket or disk, where you control both ends and the CPU never does arithmetic directly on the packed bytes.

You pack the struct, write the raw bytes to the wire, and on the receiving end you copy into a properly aligned struct before reading any field. The packed struct is a transport container, not a data structure your code operates on. The moment you read fields out of a packed struct in a running program you pay the straddle penalty on every access and you are one concurrent write away from tearing.


False Sharing

You fix your field order. You remove packed. Everything is aligned. You go multithreaded and all cores pin at 100% while throughput collapses.

struct Good is 32 bytes. Two of them fit inside one 64-byte cache line. Say your array starts at 0x1000 (4096). arr[0] lives at 0x1000 to 0x101F (4096 to 4127). arr[1] lives at 0x1020 to 0x103F (4128 to 4159). Both sit inside the single cache line spanning 0x1000 to 0x103F (4096 to 4159).

Thread 1 writes to arr[0]. Thread 2 writes to arr[1]. Different structs. No shared fields. No mutex involved. But both live in the same 64-byte cache line.

Every time Thread 1 writes to arr[0], the CPU's MESI cache coherency protocol broadcasts an invalidation across the ring bus to every other core: the cache line at 0x1000 was modified, your copies are stale, drop them. Thread 2 has its L1 cache entry for arr[1] ripped away even though nobody touched arr[1]. It takes an L1 miss, goes out to L3, fetches the 64-byte line again, modifies arr[1], and now Thread 1 gets invalidated. Back and forth. The cores spend the vast majority of their time passing one cache line across the ring bus and almost no time doing actual work.

The fix is to give each struct its own cache line:

struct __attribute__((aligned(64))) NodeMetrics {
    double balance;
    uint64_t transaction_id;
    int32_t account_type;
    int16_t region_code;
    char status;
    char currency[4];
};

Enter fullscreen mode Exit fullscreen mode

Now arr[0] owns 0x1000 to 0x103F (4096 to 4159) entirely. arr[1] owns 0x1040 to 0x107F (4160 to 4223) entirely. Thread 1 and Thread 2 never touch the same cache line and the coherency protocol never fires between them. You waste 32 bytes per struct. You get linear scaling across every core.


Takeaway

Order fields largest to smallest. Verify with sizeof. Check offsets with __builtin_offsetof when something feels off. Use packed only for wire or disk formats where you control both ends. Pad to 64 bytes with aligned(64) only when multiple threads write to adjacent elements of an array.