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

推荐订阅源

S
Security Affairs
S
Secure Thoughts
P
Proofpoint News Feed
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
Schneier on Security
V
Vulnerabilities – Threatpost
Security Archives - TechRepublic
Security Archives - TechRepublic
T
The Exploit Database - CXSecurity.com
A
Arctic Wolf
Latest news
Latest news
Hacker News - Newest:
Hacker News - Newest: "LLM"
AI
AI
T
Troy Hunt's Blog
H
Heimdal Security Blog
美团技术团队
Webroot Blog
Webroot Blog
P
Proofpoint News Feed
Hacker News: Ask HN
Hacker News: Ask HN
Google DeepMind News
Google DeepMind News
P
Privacy & Cybersecurity Law Blog
U
Unit 42
Google DeepMind News
Google DeepMind News
V2EX - 技术
V2EX - 技术
G
Google Developers Blog
N
News and Events Feed by Topic
Project Zero
Project Zero
The Register - Security
The Register - Security
N
Netflix TechBlog - Medium
IT之家
IT之家
月光博客
月光博客
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
N
News and Events Feed by Topic
Simon Willison's Weblog
Simon Willison's Weblog
L
Lohrmann on Cybersecurity
Schneier on Security
Schneier on Security
博客园_首页
Help Net Security
Help Net Security
AWS News Blog
AWS News Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
S
Security @ Cisco Blogs
PCI Perspectives
PCI Perspectives
Cisco Talos Blog
Cisco Talos Blog
C
Cybersecurity and Infrastructure Security Agency CISA
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
Docker
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
阮一峰的网络日志
阮一峰的网络日志
Spread Privacy
Spread Privacy
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Hacker News
The Hacker News

Hacker News

Introducing Claude Opus 4.7 Qwen Studio The Future of Everything is Lies, I Guess: Where Do We Go From Here? GitHub - SeanFDZ/macmind: Single-layer transformer in HyperTalk for the classic Macintosh Show HN: Agent-cache – Multi-tier LLM/tool/session caching for Valkey and Redis Bonsai 1-bit WebGPU - a Hugging Face Space by webml-community Moving a large-scale metrics pipeline from StatsD to OpenTelemetry / Prometheus GitHub - Nightmare-Eclipse/RedSun: The Red Sun vulnerability repository GitHub - SethPyle376/hiraeth: Local AWS emulator focused on fast integration testing, with SQS support, SQLite-backed state, and a debug-friendly web UI. GitHub - macOS26/Agent: Any AI, replaces Claude Code, Cursor, OpenClaw. Over 18 LLM providers (Claude, OpenAI, Gemini, Ollama, Zai, HF, Qwen) wired into a native Mac app that writes code, builds Xcode projects, bumps versions, manages git, automates Safari, use AppleScript, JS or Accessibility, extend Agent! w/ MCP Servers, run tasks from your iPhone via Messages. YouTube now lets you turn off Shorts I Made a Terminal Pager Burgers | マクドナルド公式 Commands — HackerNews CLI documentation ChatGPT for Excel PiCore - Raspberry Pi Port of Tiny Core Linux Live Nation illegally monopolized ticketing market, jury finds Google Broke Its Promise to Me. Now ICE Has My Data. Founding Engineer at Adaptional | Y Combinator CRISPR takes important step toward silencing Down syndrome’s extra chromosome GitHub - saffron-health/libretto: The AI toolkit for building reliable browser automations US v. Heppner (S.D.N.Y. 2026) no attorney-client privilege for AI chats [pdf] Retrofitting JIT Compilers into C Interpreters IPv6 – Google The Accursèd Alphabetical Clock Cybersecurity Looks Like Proof of Work Now Fragments: April 14 Cal.com Goes Closed Source: Why AI Security Is Forcing Our Decision | Cal.com - Scheduling Software for Online Bookings Laravel raised money and now injects ads directly into your agent When moving fast, talking is the first thing to break Too much Discussion of the XOR swap trick – Heather Cafe Introduction to Spherical Harmonics for Graphics Programmers The Grand Line Building a Z-Machine in the worst possible language High-Level Rust: Getting 80% of the Benefits with 20% of the Pain GitHub - duguyue100/midnight-captain: Inspired by Midnight Commander, tailored to my taste. How to build a `git diff` driver · Jamie Tanna | Software Engineer Center for Responsible, Decentralized Intelligence at Berkeley The Local Universe’s Expansion Rate Is Clearer Than Ever, but Still Doesn’t Add Up - A new synthesis of astronomical measurements confirms a persistent mismatch that could point to physics beyond current models The air throughout our homes is infused with microplastics. But there are things you can do to breathe less of them The disturbing white paper Red Hat is trying to erase from the internet – OSnews The Future of Everything is Lies, I Guess: Annoyances ‘Abhorrent’: the inside story of the Polymarket gamblers betting millions on war Productive procrastination — Max van IJsselmuiden maps, territory and LMs 447 Terabytes per Square Centimetre at Zero Retention Energy: Non-Volatile Memory at the Atomic Scale on Fluorographane Show HN: Pardonned.com – A searchable database of US Pardons 20 Years on AWS and Never Not My Job The Seasons are Wrong Artemis II crew splashes down near San Diego after historic moon mission We gave an AI a 3 year retail lease in SF and asked it to make a profit | Andon Labs How a dancer with ALS used brainwaves to perform live On filing the corners off my MacBooks Installing every* Firefox extension OpenClaw’s memory is unreliable, and you don’t know when it will break Steve Blank Nowhere Is Safe Chimpanzees in Uganda locked in vicious 'civil war', say researchers watgo - a WebAssembly Toolkit for Go linux/Documentation/process/coding-assistants.rst at master · torvalds/linux GitHub - callumlocke/json-formatter: Makes JSON easy to read. Founding Product Engineer at Bild AI | Y Combinator A compelling title that is cryptic enough to get you to take action on it GitHub - Keychron/Keychron-Keyboards-Hardware-Design: Industrial design files for Keychron keyboards and mice. 100+ models with CAD assets in STEP, DXF, DWG, and PDF. Source-available, with commercial use allowed for original compatible accessories within the license terms. [ANNOUNCE] WireGuardNT v0.11 and WireGuard for Windows v0.6 Released 1D-Chess Helium Is Hard to Replace Cooperative Vectors Introduction | Evolve Keeping a Postgres queue healthy — PlanetScale Our response to the Axios developer tool compromise Do Americans read print books, e-books or audiobooks more? The Zettelkasten Method in Obsidian: A Practical Setup Guide Artemis II Is Competency Porn and We Are Starving For It WeakC4 Flight Viz — Cockpit View A Mexican surveillance giant you’ve never heard of is now watching the U.S. border Surelock: Deadlock-Free Mutexes for Rust RISC-V 101 – what is it and what does it mean for Canonical? | Ubuntu The Problem That Built an Industry How Much Linear Memory Access Is Enough? | Solidean Investigating Split Locks on x86-64 Simplest hash functions Sybilproof reputation mechanisms (2005) [pdf] What is a property? How Complex is my Code? Static code analysis in Kotlin — tools overview Toffoli gates are all you need PGLite evangelism dcmake: a new CMake debugger UI Clojure on Fennel part one: Persistent Data Structures Fragments: April 2 Python Release Python install manager 26.1 The Life and Death of the Book Review - Liberties Introducing Database Traffic Control — PlanetScale Bitcoin miners are losing $19,000 on every BTC produced as difficulty drops 7.8% God sleeps in the minerals Building slogbox Apple Silicon and Virtual Machines: Beating the 2 VM Limit Who was “Not Even Wrong” first? Pokemon Evolution Vs Darwinian Evolution The APL Programming Language Source Code
C Constructs That Still Don’t Work in C++ — and a Few That Changed
Josh Lospinoso · 2026-05-20 · via Hacker News

C++ | May 20, 2026

A 2026 sequel to C Constructs That Don't Work in C++: what still breaks, what C++20 changed, and what C23 changed.

In 2019 I wrote a short survey of C constructs that do not work in C++. The point was not that C is sloppy or that C++ is superior. The point was that C++ is not a superset of C, and that C programmers crossing the border should know where the checkpoints are.

That advice still holds. But the border moved.

C++20 picked up a version of designated initializers. C++20 also repaired some low-level object-lifetime cases around malloc that used to be easy to describe incorrectly. C23, meanwhile, changed the old empty-parameter-list rule that made void f() mean something dangerously different in C than in C++.

The practical lesson is the same, but sharper: when you discuss C/C++ compatibility, label the language mode. “Valid C” and “valid C++” are not precise enough anymore. You often need to say C17, C23, C++17, C++20, or C++23.

I also put the examples behind this post in a small companion repository. The repository is for repeatable checks; its Compiler Explorer links are for quick diagnostics.

Compatibility matrix

Here is the short map. Details follow. The point is not just that C++ is not a superset of C. The point is that some of the canonical examples people still repeat changed under C++20 or C23, so the correct answer now depends on the language mode. This table is a map, not a substitute for the examples; on narrow screens, the sections below are easier to read than the full matrix.

StatusConstructC17C23C++17C++20 / C++23Practical advice
Still differentvoid* to object pointerImplicit conversion from malloc is idiomatic C.Same.No implicit conversion.Same.In C++, do not make malloc your default allocation strategy. If you must use it, cast deliberately and handle lifetime deliberately.
Changed since 2019malloc and object lifetimeC allocation creates storage used as objects by C’s rules.Same broad C model.Easy to write code that compiles with a cast but has no C++ object lifetime.Some implicit-lifetime cases are repaired. Constructors still are not called.Distinguish storage, lifetime, initialization, ownership, and destruction.
Still differentDiscarding constConstraint violation; compilers often warn.Same basic concern.Ill-formed without a cast.Same.A cast may compile. It does not make writes to actually-const objects defined.
Changed in C23EnumsEnumerator constants are integer-like; enum objects convert freely enough to surprise C++ programmers.C23 adds fixed underlying types and more explicit typing rules for enumerators.Enum types are distinct; int to enum is not implicit.Same; enum class remains stricter.Use enum class for C++ APIs. Use plain enums only when ABI or C interop demands it.
Changed in C23void f()No prototype in the old sense; mismatched calls may compile, but are not defined.Behaves as though declared with void.Means no parameters.Same.For shared headers, still write void f(void) in C-facing APIs unless you control the language mode.
Changed since 2019Designated initializersFull C-style designated initialization, including out-of-order, array, nested, and mixed forms.Same family, with C23 evolution elsewhere.Not standard C++.Standard, but narrower than C.Useful in C++20, but only for aggregates, direct members, declaration order, and all-designated clauses.
Extension traprestrictStandard C99 qualifier.Still standard C, with C23 wording updates.Not standard C++.Not standard C++.Use compiler extensions only behind a portability boundary.
Still differentFlexible array membersStandard C99 trailing-array pattern.Still standard C.Not standard C++.Not standard C++.Keep the C layout at the ABI edge; translate into span, vector, or an explicit header/payload representation.

Designated initializers: yes, but not C’s version

The 2019 post said designated initializers were not available in C++, with a note that they were likely coming in C++20. That note aged well.

C++20 added designated initializers for aggregate initialization. This is valid C++20:

struct Address {
  const char* street;
  const char* city;
  const char* state;
  int zip;
};

Address white_house{
  .street = "1600 Pennsylvania Avenue NW",
  .city = "Washington",
  .state = "District of Columbia",
  .zip = 20500,
};

This is not the same feature C programmers are used to.

C++ designators must name direct non-static data members, and they must appear in declaration order. That means this out-of-order form remains invalid C++:

struct Options {
  int timeout_ms;
  bool verbose = false;
  int retries = 0;
};

Options o{
  .retries = 3,       // invalid C++20: out of declaration order
  .timeout_ms = 5000,
};

C also permits patterns that C++ still rejects, including array designators and nested designators:

int table[4] = { [2] = 99 }; // valid C, invalid C++

struct Inner { int value; };
struct Outer { struct Inner inner; };
struct Outer o = { .inner.value = 7 }; // valid C, invalid C++

C also lets you mix positional and designated clauses in the same initializer:

struct Triple {
  int first;
  int second;
  int third;
};

struct Triple t = { 1, .third = 3 }; // valid C, invalid C++

In C++20, the analogous aggregate initialization is ill-formed:

struct Triple {
  int first;
  int second;
  int third;
};

Triple t{1, .third = 3}; // invalid C++20: mixed designated and positional clauses

This is not arbitrary. C++ has constructors, destructors, default member initializers, references, and an order-of-initialization model that code can observe. C-style freedom would collide with the C++ object model.

There are proposals to loosen the C++ rules, including out-of-order designated initializers and base-class designated initialization. Treat those as proposals. Do not write portable C++ on the assumption that they have landed.

Rule: C++20 designated initializers are great for plain aggregate configuration objects. They are not a drop-in replacement for C99 designated initialization. C++20’s form is not “C designators, now in C++.” It is a constrained aggregate-initialization feature. The useful mental model is: direct members, declaration order, and do not mix designated and non-designated clauses.

Empty parameter lists: C moved toward C++

This used to be one of the cleanest examples of C and C++ disagreement.

In C++:

void fn();

fn(42); // invalid C++: fn takes no arguments

In C17 and earlier, void fn(); did not provide a prototype. A definition written void fn() {} specified no parameters, but calls made through a non-prototype declaration were not checked the way C++ programmers expect. Such a call might compile after default argument promotions, but if the number of supplied arguments does not match the number of parameters, the behavior is undefined:

void fn() { }

int main(void) {
  fn(42); // may compile in C17 mode; undefined for this definition
}

C23 removes the old split: a function declarator without a parameter type list behaves as if it used void, provides a prototype, and the argument count must agree.

That is a real compatibility improvement. It also creates a migration wrinkle: older C code may compile in C17 mode and fail in C23 mode. That is good failure, but it is still failure.

Rule: in C-facing headers, void fn(void) remains the least surprising spelling. In C++-only code, void fn() is fine.

void*, malloc, and the object-lifetime trap

The simple incompatibility is unchanged. C lets you write this:

int* values = malloc(100 * sizeof *values);

C++ does not implicitly convert void* to int*:

int* values = std::malloc(100 * sizeof *values); // invalid C++

You can cast:

auto* values = static_cast<int*>(std::malloc(100 * sizeof(int)));

But the cast is not the interesting part. The interesting part is lifetime.

In current C++, the sharp edge is not “malloc can never give you objects.” C++20 narrowed that. Some operations, including C allocation functions, are specified to implicitly create objects of implicit-lifetime types if doing so would make the program defined; the draft’s example is essentially a trivial aggregate returned by std::malloc and then assigned through its members. That repair is deliberately limited: it does not run constructors, initialize scalar values, establish invariants, or start lifetimes for subobjects that are not themselves implicit-lifetime types. For non-implicit-lifetime types, storage is still just storage until construction happens.

So this kind of code is no longer the best scare example in C++20:

#include <cstdlib>

struct X {
  int a;
  int b;
};

X* make_x() {
  auto* p = static_cast<X*>(std::malloc(sizeof(X)));
  p->a = 1;
  p->b = 2;
  return p;
}

For an implicit-lifetime type like X, C++20 repairs the lifetime issue. That does not make malloc idiomatic C++.

The repair does not call constructors. It does not initialize values. It does not give you exception safety. It does not pair ownership with destruction. It does not make this OK:

#include <cstdlib>
#include <string>

void bad() {
  auto* s = static_cast<std::string*>(std::malloc(sizeof(std::string)));
  *s = "hello"; // undefined behavior: no std::string object was constructed
}

The safe low-level C++ spelling is explicit:

#include <memory>
#include <new>
#include <string>

void* storage = ::operator new(sizeof(std::string));
auto* s = new (storage) std::string("hello");

std::destroy_at(s);
::operator delete(storage);

The better high-level spelling is usually simpler:

auto s = std::make_unique<std::string>("hello");

Rule: a cast from void* is never the whole story. Ask five questions: who owns the storage, when does the object lifetime begin, how is the object initialized, who destroys it, and what happens on failure?

const_cast: compiles is not the same as defined

The old post pointed out that C++ forces you to be explicit when discarding const:

const int x = 100;
int* p = &x; // invalid C++

You can write the cast:

const int x = 100;
int* p = const_cast<int*>(&x);

But this only removes the type-system barrier. It does not change the object. Writing through p is undefined behavior because x is actually a const object.

There is a valid use case:

int x = 100;
const int* view = &x;
int* p = const_cast<int*>(view);
*p = 101; // defined: the original object is not const

That distinction matters in legacy integration. Sometimes a C API takes char* even though it promises not to mutate the buffer. A const_cast at that boundary can be the least-bad option. Put it at the edge, document it, and keep it out of the core logic.

Do not use this trick for string literals, memory-mapped read-only storage, or objects originally declared const. If the legacy function actually writes, the cast only moves the bug.

Rule: const_cast is not a permission slip. It is a localized escape hatch.

Enums: less simple than “C uses int”

The old shorthand “C enum values are backed by int” is too compressed for a 2026 version of this article. For C17, the safer shorthand is: enumerator constants have integer type, while the enumerated type itself is compatible with an implementation-defined integer type capable of representing its values. C23 makes the model more explicit: every enumeration has an underlying type, a fixed underlying type can be written, and the type of an enumeration constant after completion depends on whether the enumeration has a fixed underlying type and whether the values fit in int.

That is still not C++‘s model. In C++, an enumeration is a distinct type. An unscoped enumerator, or an object of unscoped enumeration type, can participate in integral promotion or conversion, but an arbitrary integer is not assignable to the enum without a cast. A scoped enum does not implicitly convert to int or bool.

enum Mode { off = 0, on = 1 };

int x = on; // OK: unscoped enum to int
Mode m = 1; // invalid C++: int to Mode is not implicit

If you need to cross from an integer representation, say so:

Mode m = static_cast<Mode>(1);

For C++ APIs, prefer scoped enums:

enum class Mode : unsigned {
  off = 0,
  on = 1,
};

int x = Mode::on; // invalid C++
auto y = static_cast<unsigned>(Mode::on); // explicit

Rule: use plain enums when they are part of a C ABI or when you intentionally want old enum behavior. Use enum class when the enum is a domain type in C++.

restrict: a C promise, not a C++ contract

C99 introduced restrict so a programmer could promise that a pointer is the unique access path to an object for a period of execution. That promise can unlock useful aliasing optimizations. If the promise is false, the behavior is undefined.

Standard C++ has no restrict keyword. GCC and Clang support __restrict__ and __restrict as extensions. MSVC has __restrict for variables and __declspec(restrict) for function declarations and definitions, with return-value aliasing semantics. Treat all of these as toolchain contracts, not portable C++ interface design.

Rule: if you need restrict-like semantics in C++, isolate the extension in a small boundary, test it with the compilers you actually ship, and make the aliasing precondition impossible to miss.

Flexible array members: keep them at the edge

C99 also standardized flexible array members:

struct Packet {
  unsigned length;
  unsigned char payload[];
};

This is a good C pattern for a variable-length object with a fixed header and trailing data. It is not standard C++.

Some C++ compilers accept flexible array members as extensions. That does not make the code portable C++. It also does not solve the lifetime and ownership questions that C++ is trying to force into the open.

In C++, usually choose one of these instead:

struct Packet {
  unsigned length;
  std::vector<std::byte> payload;
};

or, when the storage is owned elsewhere:

struct PacketView {
  unsigned length;
  std::span<const std::byte> payload;
};

At an ABI boundary, you may need to preserve the C representation. That is fine. But keep it quarantined. Parse the C layout, validate lengths, then translate into a C++ representation with explicit ownership or a bounded view.

Rule: flexible array members are a C layout tool. They are not a portable C++ object model.

Migration rules

When moving C habits into C++, I use these rules:

  1. Label the language mode before making the claim.
  2. Do not assume “works in C” means “is C++ with warnings.”
  3. Do not confuse “compiles with a cast” with “has defined behavior.”
  4. Treat malloc as storage, not construction.
  5. Preserve C layouts at ABI boundaries, then translate into C++ types.
  6. Prefer C++ constructs that make ownership and lifetime visible.
  7. Use compiler extensions only behind named, tested portability boundaries.

The old lesson still stands: C++ is not a superset of C. The updated lesson is more precise: the languages increasingly share syntax, but they do not share the same object model, initialization model, or invariants.

That is where the bugs hide.

References