<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hacker News: el_pollo_diablo</title><link>https://news.ycombinator.com/user?id=el_pollo_diablo</link><description>Hacker News RSS</description><docs>https://hnrss.org/</docs><generator>hnrss v2.1.1</generator><lastBuildDate>Sun, 27 Sep 2026 10:16:47 +0000</lastBuildDate><atom:link href="https://hnrss.org/user?id=el_pollo_diablo" rel="self" type="application/rss+xml"></atom:link><item><title><![CDATA[New comment by el_pollo_diablo in "The science of Monkey Island: can grog dissolve a metal mug that fast?"]]></title><description><![CDATA[
<p>¡He dejado en libertad los prisioneros y ahora vengo por ti!</p>
]]></description><pubDate>Fri, 25 Sep 2026 13:44:19 +0000</pubDate><link>https://news.ycombinator.com/item?id=49844591</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49844591</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49844591</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>> declared in separate translation units<p>Now if I include both library headers in code that attempts to plug them together, the types will be incompatible.<p>Although not a proof in itself, GCC and Clang seem to agree: <a href="https://godbolt.org/z/1ocr5Go5b" rel="nofollow">https://godbolt.org/z/1ocr5Go5b</a>.</p>
]]></description><pubDate>Fri, 04 Sep 2026 07:31:11 +0000</pubDate><link>https://news.ycombinator.com/item?id=49561626</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49561626</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49561626</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>> Taking the pointer to payload[] field is in no way different from doing "(char*) x + offsetof(struct node, payload)"<p>It may differ, depending on the precise notion of provenance being applicable. If provenance only has allocation granularity, I suppose that there is no difference. I know that there were some discussions about provenance and subobjects. I do not know whether the question is resolved.<p>Where this gets complicated is that zero-sized arrays are non-standard. So even if we could build a convincing argument from standard notions of provenance, how would it transfer to a subobject that is excluded from the standard?<p>Last but not least, this is not only about creating the effective type through memcpy. The question is also whether this destroys the effective type of the structure. See my previous point about possible padding after 'payload'.<p>Please also consider that flexible array members are here for a reason. If I follow your argument, then they bring nothing that arrays of length 0 or 1 do not already cover.</p>
]]></description><pubDate>Fri, 04 Sep 2026 07:22:28 +0000</pubDate><link>https://news.ycombinator.com/item?id=49561555</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49561555</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49561555</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>> how does this work?<p>Like this:<p><pre><code>  struct thread {
      // Entry in the list of threads of the containing process
      list_node process_entry;
      // Entry in this thread's scheduling queue
      list_node sched_entry;
      // ...
  };
</code></pre>
Each struct thread is linked in two lists. When we need to get from a list_node * to the enclosing struct thread, we know from the context which list is being inspected, so we know which one of process_entry or sched_entry to consider for offsetting the pointer.</p>
]]></description><pubDate>Thu, 03 Sep 2026 22:09:14 +0000</pubDate><link>https://news.ycombinator.com/item?id=49557819</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49557819</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49557819</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>We mean compatible as defined by the C language standard. It is much more restrictive than having the same layout. In particular, you may not pass a pointer to a type where a pointer to an incompatible type is expected, even if the types have the same layout, which prevents the sort of sharing between two libraries that is being discussed.<p>Moreover, there is no guarantee that two distinct structure types with the same list of members have the same size or alignment (although in practice they do). The members must nevertheless be laid out in the same way (same offsets, and in the case of bit-fields, same layout inside storage units) due to an obscure constraint on <i>common initial sequences</i>. So the layouts of the structures may differ in the alignment requirement and the amount of trailing padding.</p>
]]></description><pubDate>Thu, 03 Sep 2026 19:03:40 +0000</pubDate><link>https://news.ycombinator.com/item?id=49555025</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49555025</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49555025</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>> The structs list_of_A and list_of_B are compatible.<p>No, they are not. From C23, 6.7.3.4 Tags: <i>Each declaration of a structure, union, or enumerated type which does not include a tag declares a distinct type.</i></p>
]]></description><pubDate>Thu, 03 Sep 2026 17:32:33 +0000</pubDate><link>https://news.ycombinator.com/item?id=49553646</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49553646</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49553646</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>> IIRC GCC and Clang lets character types alias to any type.<p>It is always legal to access the memory representation of any object as an array of characters. The other way around (interpreting an array of characters as a T, even though it does not have effective type T) is not.<p>> Otherwise glibc’s malloc also doesn’t abide to strict aliasing.<p>It may not have to. From the point of view of C, malloc is special because it is part of the implementation. The compiler is free to handle UB as it sees fit. In particular, it can decide that aliasing has different semantics in malloc.c than outside it.</p>
]]></description><pubDate>Thu, 03 Sep 2026 17:24:07 +0000</pubDate><link>https://news.ycombinator.com/item?id=49553519</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49553519</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49553519</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>> It does matter, for malloc-returned storage. You can put whatever objects you want into that storage as long as it fits and the pointer is properly aligned.<p>You can certainly store an object of arbitrary type, but here it is done through a pointer to an object with pointer arithmetic going beyond the allowed bounds.<p>> memcpy takes a void pointer as its destination, sets the effective type of the storage behind it<p>And, in doing so, may very well overwrite the unspecified padding following 'payload' in the structure, thus instantly destroying the effective type of the structure object itself. Subsequent accesses to the structure or its members will be UB.<p>It seems to me that your argument hinges on two assumptions:<p><pre><code>    - there is no padding following 'payload' (this would have to be statically asserted),
    - the pointer to 'payload' is indistinguishable from the pointer past the structure; in particular, provenance is not an issue.
</code></pre>
That is a very interesting discussion.</p>
]]></description><pubDate>Thu, 03 Sep 2026 17:18:08 +0000</pubDate><link>https://news.ycombinator.com/item?id=49553434</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49553434</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49553434</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>> as long as the struct tags are the same<p>Exactly. Now you have a naming problem. You need a naming convention that every user of the list library must follow, or else their types will be incompatible. And what about typedefs? If A is a typedef of B, or more generally A and B are typedef-related (their normal forms, obtained by following all typedefs, are the same), lists of A and B will be incompatible unless users agree on a common name. The only realistic choice is the normal form, but then this actively works against the abstraction provided by typedef.<p>And this is just for types. What about functions? While it is legal to do identical definitions of struct list_int, it is not for list_int_init() and list_int_add(). Or global variables: it is legal to do several identical extern declarations, but there can only be one definition; which compilation unit gets to do it?</p>
]]></description><pubDate>Thu, 03 Sep 2026 16:44:30 +0000</pubDate><link>https://news.ycombinator.com/item?id=49552956</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49552956</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49552956</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>> There is no OoB access of an array<p>Yes, there is. It does not matter that storage happens to be allocated beyond the end of said array. Strict aliasing implies that it is UB to reinterpret the array as anything else. And it is UB to access an array out of bounds.<p>Flexible array members specifically exist for these dynamically-allocated trailing arrays. They do not solve the strict aliasing problem, though.<p>> if the payload type has an alignment that's greater than the size of a pointer<p>The amount of padding is implementation-defined. The only portable guarantee is that 'payload' is aligned for its element type, char. To over-align, use _Alignas, as in:<p><pre><code>    struct node {
        struct node *next;
        _Alignas(max_align_t) char payload[]; // Satisfies all fundamental alignment requirements
    };</code></pre></p>
]]></description><pubDate>Thu, 03 Sep 2026 16:14:51 +0000</pubDate><link>https://news.ycombinator.com/item?id=49552454</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49552454</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49552454</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>> I do not think a type-safe macro-generated list in C is any more awkward to implement or inferior to a C++ template version.<p>I have some experience with this, and while this is one of these things that are feasible, I find them significantly inferior to templates in practice.<p>For one thing, footguns are everywhere in C macro-based metaprogramming. E.g. do not declare the payload as 'payload_type payload;' in the node structure, but choose 'typeof(payload_type) payload;' instead, as someone may pass an array type or function pointer type for 'payload_type'. Speaking of array types, how do you deal with the fact that you cannot pass them by value? I will choose C++ templates' semantic substitution over C macros' textual substitution.<p>Anyway, to me, the biggest limitation of macro-generation compared to templates is that there is no centralized monomorphization. If an application uses two libraries, each of which handles lists of int, each library will have to independently macro-generate its separate list implementation, and because C's type system is nominal, the generated types will be isomorphic but incompatible. Contrast this with C++ templates, where two independent libraries can happily share std::list<int> values.</p>
]]></description><pubDate>Thu, 03 Sep 2026 16:05:24 +0000</pubDate><link>https://news.ycombinator.com/item?id=49552289</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49552289</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49552289</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>Zero-sized arrays are not standard. Accessing an array out of bounds is UB. At the very least, you should use a flexible array member instead (char payload[];).<p>But even if you did that, strict aliasing implies that 'payload' can only be accessed as an array of character type. It is correct to memcpy between 'payload' and another object of arbitrary type T of the appropriate size (as list_push_ does in your example), but it is UB to access 'payload' in place as a T (as main does, by casting to struct point * and dereferencing). Oh, and 'payload' may not satisfy the alignment requirement of T.<p>There is no realistic strict-aliasing-abiding way around a distinct node type per payload type.</p>
]]></description><pubDate>Thu, 03 Sep 2026 15:34:54 +0000</pubDate><link>https://news.ycombinator.com/item?id=49551736</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49551736</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49551736</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Intrusive linked lists (2019)"]]></title><description><![CDATA[
<p>The go a bit further than the article on the advantages of intrusive data structures, taking linked lists as an example:<p>As the article mentions, intrusive data structures naturally lead to one fewer indirection. To do the same with a traditional list (where the list node owns the payload), a different node type is needed for each payload type. This is easy to do with the proper support for monomorphized generics, see C++'s std::list. It is awkward in C, where the implementation has to be macro-generated. C naturally pushes towards an indirection through void *, which makes intrusive lists more attractive.<p>One other advantage of intrusive data structures is the ability to link a payload into several parallel collections without indirections (where traditional collections would require e.g. one collection owning the payloads, and the other collections merely holding non-owning pointers to them).<p>Las but not least, the defining property of intrusive data structures is that they leave the responsibility of allocating the elements to the user. The elements can be allocated on the heap, on the stack, in a global array (like "initholes" in the article), in a special arena, etc. It is even reasonable to use non-uniform allocation strategies; for example, for a circular list, allocate an anchor node on the stack and the other nodes (those embedded in payloads) on the heap.</p>
]]></description><pubDate>Thu, 03 Sep 2026 13:14:38 +0000</pubDate><link>https://news.ycombinator.com/item?id=49549542</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49549542</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49549542</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "We have proof automation now"]]></title><description><![CDATA[
<p>> a single proof covering the most precise description of the program's behavior is more compact<p>Yes, and a program is most compact when all modules have been merged, and all functions with a single caller inlined. We have compilers with LTO for that, though; we would never maintain source code in that form.<p>Snark aside, I get your point about restating the program code, but this can be alleviated by interactive proof assistants that largely reduce the length of proof scripts. This is mostly a matter of tooling.</p>
]]></description><pubDate>Mon, 27 Jul 2026 13:33:49 +0000</pubDate><link>https://news.ycombinator.com/item?id=49069500</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49069500</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49069500</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "We have proof automation now"]]></title><description><![CDATA[
<p>In my view, a major selling point of dependent types when it comes to reasoning, is that by bundling logical properties with a runtime value, they require no separate effort to prove the propagation of the logical properties as the value is moved around. That is why I mentioned them in the context of opaque types. This is especially useful with generics: when a type parameter is instantiated with a dependent type, all its occurrences instantly benefit from the strong typing.<p>They can also be used to enforce just enough constraints on the inputs of a function to make it total, but this comes down to the tradeoff between either leaving an error path in the program and proving its unreachability later, or not having this error path but immediately requiring the proof. In any case, as you mention, further properties can be proved later without altering the dependent type.<p>I do not intend to come off as overly negative about dependent types. They have their uses, but they can also bring a maintenance nightmare.</p>
]]></description><pubDate>Mon, 27 Jul 2026 13:22:12 +0000</pubDate><link>https://news.ycombinator.com/item?id=49069363</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49069363</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49069363</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "We have proof automation now"]]></title><description><![CDATA[
<p>I am not sure what you mean. Of course proving a new property generally implies reasoning on each elementary step of the program. My point is that, assuming you have already proved a property, proving a new one shouldn't lead you to alter the first proof. But it does if you carelessly use some logical tools like dependent types and single preconditions/postconditions/loop invariants.<p>For example, take Hoare's while rule (see <a href="https://en.wikipedia.org/wiki/Hoare_logic#While_rule" rel="nofollow">https://en.wikipedia.org/wiki/Hoare_logic#While_rule</a>). If you have already proved<p><pre><code>    {P∧B}S{P}
</code></pre>
and, in order to prove another property, some new invariant Q has to be propagated across this loop. It would be enough to prove<p><pre><code>    {Q∧B}S{Q}
</code></pre>
or even<p><pre><code>    {P∧Q∧B}S{Q}
</code></pre>
if the new proof builds upon the first one. But if your logical tool of choice insists on having a unique loop invariant, you must throw away your existing proof and prove<p><pre><code>    {P∧Q∧B}S{P∧Q}
</code></pre>
which is incomparable and uselessly mixes both concerns.</p>
]]></description><pubDate>Mon, 27 Jul 2026 12:52:36 +0000</pubDate><link>https://news.ycombinator.com/item?id=49068969</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49068969</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49068969</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "We have proof automation now"]]></title><description><![CDATA[
<p>I have already written it, and I will write it again: dependent types and total functions do not scale. Maintenance is terrible.<p>Suppose that you have managed to write a non-trivial piece of software with dependent types encoding all sorts of properties everywhere. The actual computation and proof are intermingled. Think of the author's innocent bound-check proof in the zstd decoder, but across the whole program, with more elaborate properties and longer proofs.<p>Suddenly, you realize that you need to prove a new property of your program. Can you keep your existing work and build on top of it? In general, no, you have to refine every dependent type everywhere by adding a new conjunct expressing a new invariant, and adapt every proof, as the new property is threaded in the existing program.<p>That is because dependent types (and other staples of naive approaches to proving program properties, like a unique invariant per loop) structure the program along the wrong dimension: they encourage grouping everything that concerns a value ("put this value in a dependent type that encodes everything known about it") or a program point ("write the precondition for this function as a big conjunction mixing all the concerns"), where it works much better, for long-term maintenance, to structure the development along concerns: computational parts of the program, basic functional properties and absence of UB, termination, other functional properties, security, etc., where each layer builds on top of the previous ones without requiring them to change.<p>That is not to say that dependent types do not have their use. Where they shine is at module boundaries. Consider a library that exposes an opaque type and operations on it. Users of the library can only build and modify values of that type through the library's API. This type should be a dependent type. If it needs to be refined at any point to encode a new invariant, this will have no impact on the library's users, since all they do is pass around values without interpreting them. However, inside the library, I would recommend unpacking/repacking the dependent type at the library's entry points and handling the concerns separately.</p>
]]></description><pubDate>Mon, 27 Jul 2026 07:58:24 +0000</pubDate><link>https://news.ycombinator.com/item?id=49066421</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=49066421</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=49066421</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Everything in C is undefined behavior"]]></title><description><![CDATA[
<p>> probably meaning on an address that’s a multiple of sizeof(int), but who knows<p>Sigh. s/sizeof(int)/_Alignof(int)/.<p>There are good reasons for an implementation to have sizeof(int) = _Alignof(int) and not a mere multiple of it, but if you are going to discuss subtle points and UB, just stick to the language guarantees.<p>> But let’s say you have a modern machine, where NULL is a pointer to address zero, and you actually have an object there.<p>You don't program in C on such a machine. Or maybe memory is virtualized, and it does not matter that your object lives at physical address zero, as long as you can map a non-zero virtual address to it.<p>> So how do you print an uid_t?<p><pre><code>    if ((uid_t)-1 < (uid_t)0) {
        // uid_t is signed
        printf("%" PRIdMAX, (intmax_t)id);
    } else {
        // uid_t is unsigned
        printf("%" PRIuMAX, (uintmax_t)id);
    }
</code></pre>
> It’s not rare for the denominator to come from untrusted input.<p>It's not rare for the array index to come from untrusted input.<p>It's not rare for the supposedly valid UTF-8 string to come from untrusted input.<p>...<p>Why single out division? This problem affects every partially defined operation. In the case of division at least, everyone learned in school that thou shalt not divide by zero. Adding two untrusted integers and forgetting that signed overflow is UB, not defined as a modulo? Your average programmer is much less likely to see that coming.<p><pre><code>    > unsigned char a = 0xff;
    > unsigned char b = 1;
    > unsigned char zero = 0;
    > bool overflowed = (a + b) == zero;
    >
    > unsigned char a = 0x80;
    > uint64_t b = a << 24;
</code></pre>
Please. Convert your operands to wide enough types before the operation. Convert your results back to narrow enough types to compensate for integer promotion to wider types than you would have liked. Do that consistently, and you're good.<p>Here:<p><pre><code>    unsigned char a = 0xff;
    unsigned char b = 1;
    unsigned char zero = 0;
    bool overflowed = (unsigned char)(a + b) == zero;

    unsigned char a = 0x80;
    uint64_t b = (uint32_t)a << 24;</code></pre></p>
]]></description><pubDate>Wed, 20 May 2026 15:59:14 +0000</pubDate><link>https://news.ycombinator.com/item?id=48209876</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=48209876</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48209876</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Int a = 5; a = a++ + ++a; a =? (2011)"]]></title><description><![CDATA[
<p>Type punning via unions is not UB in C in general, but it is in C++ IIRC.<p>I write "in general" because, as with other forms of memory reinterpretation (memcpy or copy through a character type), evaluating a trap representation triggers UB.</p>
]]></description><pubDate>Thu, 14 May 2026 21:20:01 +0000</pubDate><link>https://news.ycombinator.com/item?id=48141406</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=48141406</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48141406</guid></item><item><title><![CDATA[New comment by el_pollo_diablo in "Box to save memory in Rust"]]></title><description><![CDATA[
<p>So there are now two ways to represent the same state: None or Some(struct whose fields are all None). Even though one of these representations is never produced by the deserialization routine, anyone could construct it if the constructor is public. And even if they don't, the different representations will show up in pattern matching as separate paths for every access to the field. This looks like a good opportunity to make these types (optimized for storage) private, and to define public view objects/accessors (optimized for usage) on top of them that merge equivalent representations.</p>
]]></description><pubDate>Mon, 27 Apr 2026 09:27:15 +0000</pubDate><link>https://news.ycombinator.com/item?id=47919458</link><dc:creator>el_pollo_diablo</dc:creator><comments>https://news.ycombinator.com/item?id=47919458</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=47919458</guid></item></channel></rss>