[HN Gopher] Getting silly with C, part (void*)2
___________________________________________________________________
Getting silly with C, part (void*)2
Author : justmarc
Score : 105 points
Date : 2025-01-10 17:16 UTC (5 hours ago)
(HTM) web link (lcamtuf.substack.com)
(TXT) w3m dump (lcamtuf.substack.com)
| sylware wrote:
| C syntax is already way too rich and complex.
|
| We need a C- ore uC:
|
| No implicit cast except for literals and void* (explicit compile
| time/runtime casts), one loop statement (loop{}), no
| switch/enum/generic/_thread/typeof/etc, no integer promotion,
| only sized primitive types (u64 s32 f32 etc...), no anonymous
| code block, real compiler hard/compile time constant declaration,
| many operators have to go (--,++, a?b:c, etc)... and everything I
| am forgetting right now (the dangerous struct pack attribute...).
| But we need inline keywords for memory barriers, atomics for
| modern hardware architecture programming.
| short_sells_poo wrote:
| Sooo, assembly :)
| poincaredisk wrote:
| No. Assembly is not portable, not typed and not structured
| (as in structured programming).
|
| Another usecase for microC: most decompilers decompile to a
| custom C-like language. It pretends to be C, but in reality
| this is just a representation of a recovered AST that is
| often-but not always-a valid C code. MicroC would be a better
| target, because it would supposedly have less weird edge
| cases.
| 9rx wrote:
| _> Assembly [...] not typed and not structured (as in
| structured programming)._
|
| That depends on the assembly language. Some have structure
| constructs, some are typed. Portability is out.
|
| But if you accept a slightly higher abstraction,
| WebAssembly is portable, typed, and structured.
| PittleyDunkin wrote:
| > But if you accept a slightly higher abstraction,
| WebAssembly is portable, typed, and structured.
|
| Does WebAssembly support AOT compilation to native
| binaries? I thought it was just a VM.
| jcranmer wrote:
| I'd argue that unsafe Rust is a better target here
| (although I don't know if &raw has made it into stable Rust
| yet, which you need for doing an address-of that doesn't
| have a risk of UB like & does). Rust's typesystem is less
| silly (there's only one multiple-types-for-same-value,
| namely usize), there's no implicit conversions, the core
| CFG primitives are a little bit richer (labeled
| break/continue, although those are now added in C2y), and
| there's a decent usable intrinsic class for common
| operators-not-in-C like rotations or popcount.
|
| If your goal is a simple language to compile, well, Rust
| won't ever fit the bill; but as a target for a decompiler,
| I think the unsafe subset is worth exploring.
| steveklabnik wrote:
| &raw landed in the release just before this latest one.
|
| And even before that release, there were macros the poly
| fill it, so you could have used those to target an
| earlier version.
| uecker wrote:
| Why would you target a language that is less portable and
| less stable?
| jcranmer wrote:
| For the specific question of decompiling, as noted in the
| GP comment, you're already forced to decompile to a not-
| quite-C language because C's semantics just aren't
| conducive to accurately representing the hardware
| semantics. Not that Rust doesn't have its own foibles,
| but I suspect its core semantics are easier to map to
| than C's semantics without having to resort as much to a
| not-quite-Rust target.
|
| It's definitely something I would like to see at least
| explored!
| uecker wrote:
| I am pretty sure somebody will do this. My guess is that
| it will make basically no difference, but the end result
| is likely less useful...
| uecker wrote:
| I don't think any weird edge case is a problem when
| targeting C. You just do not produce such cases when
| emitting the code.
| Frenchgeek wrote:
| Sphinx C-- maybe?
| accelbred wrote:
| Does Zig fit your bill?
| glouwbug wrote:
| C really just needs if / else / while / and void functions.
| Function inputs should be in/out (const type* or type*).
| bregma wrote:
| So, FORTRAN IV except for the else.
| mystified5016 wrote:
| You want assembly with some sugar.
|
| Read up on Forth languages. It's pretty much exactly what
| you're after.
| mananaysiempre wrote:
| Forth is kind of weak dealing with value types of unknown
| size. For example, suppose you're writing a cross-compiler,
| and an address on the target machine might take one or two
| cells on the host machine depending on the host bitness. Now
| suppose you need to accept a (host-machine) cell and a
| target-machine address (e.g. an opcode and an immediate) and
| manipulate them a bit. Trivial in C, definitely _possible_ in
| Forth, but supremely annoying and the elegance kind of falls
| apart.
| butterisgood wrote:
| Pre-scheme?
| wongarsu wrote:
| There is C0, a stripped-down version of C popular in academia
| [1]. Great for teaching because it's conceptually simple and
| easy to write a compiler for. But with a couple of additions
| (like sized primitive types) it might match what you are
| imagining
|
| 1: https://c0.cs.cmu.edu/docs/c0-reference.pdf
| GranPC wrote:
| Can anyone explain how the last snippet works?
| a12k wrote:
| Basically they execute in order of array initialization, not
| index order, so it outputs hello cruel world rather than cruel
| world hello.
| andreyv wrote:
| It creates a compound literal [1] of type array of int, and
| initializes the specified array positions using designated
| initializers [2] with the results of calls to puts().
|
| Using designated initializers without the = symbol is an
| obsolete extension.
|
| [1] https://gcc.gnu.org/onlinedocs/gcc/Compound-Literals.html
| [2] https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html
| matheusmoreira wrote:
| Note that this is GNU C, not standard C. GNU has extended the
| normal C language with features such as forward parameter
| declarations and numeric ranges in switch cases. Lots of people
| don't know about these things.
| dzaima wrote:
| Note that switch case ranges might be coming in C2y though.
| mananaysiempre wrote:
| Also forward parameter declarations, or is that proposal
| dead?
| wahern wrote:
| Basically dead. The main motivation would be to make it
| easier to use variably modified types in function
| parameters, where the (length) identifier is declared after
| the variably modified type, as in > void
| foo(int a[m][m], int m)
|
| Currently you can only do: > void foo(int
| m, int a[m][m])
|
| The holy grail is being able to update the prototypes of
| functions like snprintf to something like:
| > int snprintf(char buf[bufsiz], size_t bufsiz, const char
| *, ...);
|
| However, array pointer decay means that foo above is
| _actually_ : > void foo(int (*a)[m], int m)
|
| Likewise, the snprintf example above would be little
| different than the current definition.
|
| There's related syntax, like > foo (int m,
| int a[static m])
|
| But a is still just a pointer, and while it can help some
| static analyzers to detect mismatched buffer size arguments
| at the call site, the extent of the analysis is very
| limited as decay semantics effectively prevent tracing the
| propagation of buffer sizes across call chains, even
| statically.
|
| There's no active proposal at the moment to make it
| possible to pass VM arrays (or rather, array references)
| directly to functions--you can only pass pointers to VM
| array types. That actually works (sizeof *a == sizeof (int)
| * m when declaring int (*a)[m] in the prototype), but the
| code in the function body becomes very stilted with all the
| syntactical dereferencing--and it's just syntactical as the
| same code is generated for a function parameter of `int
| (*a)[m]` as for `int *a` (underneath it's the same pointer
| value rather than an extra level of memory indirection).
| There are older proposals but they all lost steam because
| there aren't any existing implementation examples in any
| major production C compilers. Without that ability, the
| value of forward declarations is greatly diminished.
| Because passing VM array types to functions already
| requires significant refactoring, most of the WG14 felt it
| wasn't worth the risk of adopting GCC's syntax when
| everybody could (and should?) just start declaring size
| parameters before their respective buffer parameters in new
| code.
| Gibbon1 wrote:
| That's related to something I would like which is to be
| able to set the number of elements in an incomplete
| struct. struct foo {
| size_t elements; int data[]; };
| foo foo123 = {.elements = array_size(data), .data = {1,
| 2, 3}};
|
| or struct str { size_t sz; char str[]; };
| str s123 = {.sz = strlen(.str), .str = "123"};
| uecker wrote:
| Clang and GCC just got the [[counted_by()]] attribute to
| help protect such structs in the kernel. But yes, native
| syntax for this would be nice.
| uecker wrote:
| I hope it is not "basically" dead. I just resubmitted it
| at the request of several people.
|
| And yes, for new APIs you could just change the order,
| but it does help also with legacy APIs. It does even when
| not using pointers to arrays:
| https://godbolt.org/z/TM5Mn95qK (I agree that new APIs
| should pass a pointer to a VLA).
|
| (edited because I am agreeing with most of what you said)
| mananaysiempre wrote:
| > everybody could (and should?) just start declaring size
| parameters before their respective buffer parameters in
| new code
|
| I know that was a common opinion pre-C23, but it feels
| like the committee trying to reshape the world to their
| desires (and their designs). It's a longstanding
| convention that C APIs accept (address, length) pairs in
| that order. So changing that will already get you a score
| of -4 on the Hard to Misuse List[1], for "Follow common
| convention and you'll get it wrong". (The sole old
| exception in the standard is the signature of main(), but
| that's somewhat vindicated by the fact that nobody really
| needs to call main(); there is a new exception in the
| standard in the form of Meneide's conversion APIs[2],
| which I _seriously dislike_ for that reason.)
|
| The reason I was asking is that 'uecker said it was
| requested at the committee draft stage for C23 by some of
| the national standards orgs. That's already ancient
| history of course, but I hoped the idea itself was still
| alive, specifically because I don't want to end up in the
| world where half of C APIs are (address, length) and half
| are (length, address), when the former is one of the few
| C conventions most everyone agrees on currently.
|
| [1]
| https://ozlabs.org/~rusty/index.cgi/tech/2008-04-01.html
|
| [2] https://thephd.dev/_vendor/future_cxx/papers/C%20-%20
| Restart...
| svilen_dobrev wrote:
| hehe. similar to
|
| How to Get Fired Using Switch Statements & Statement Expressions:
|
| https://blog.robertelder.org/switch-statements-statement-exp...
| kazinator wrote:
| Without information about how identifiers are declared, you do
| not know how to parse this: (A)(B);
|
| It could be a cast of B to type A, or function A being called
| with argument B.
|
| Or this (like the puts(puts) in the article):
| A(B):
|
| Could be a declaration of B as an identifier of type A, or a call
| to a function A with argument B.
|
| Back in 1999 I made a small C module called "sfx" (side effects)
| which parses and identifies C expressions that could plausibly
| contain side effects. This is one of the bits provided in a small
| collection called Kazlib.
|
| This can be used to make macros safer; it lets you write a
| #define macro that inserts an argument multiple times into the
| expansion. Such a macro could be unsafe if the argument has side
| effects. With this module, you can write the macro in such a way
| that it will catch the situation (albeit at run time!). It's like
| a valgrind for side effects in macros, so to speak.
|
| https://git.savannah.gnu.org/cgit/kazlib.git/tree/sfx.c
|
| In the sfx.c module, there is a rudimentary C expression parser
| which has to work in the absence of declaration info. In other
| words it has to make sense of an input like (A)(B).
|
| I made it so that when the parser encounters an ambiguity, it
| will try parsing it both ways, using backtracking via exception
| handling (provided by except.c). When it hits a syntax error, it
| can backtrack to an earlier point and parse alternatively.
|
| Consider (A)(A+B). When we are looking at the left part (A), that
| could plausibly be a cast or declaration. In recursive descent
| mode, we are going left to right and looking at left derivations.
| If we parse it as a declaration, we will hit a syntax error on
| the +, because there is no such operator in the declarator
| grammar. So we backtrack and parse it as a cast expression, and
| then we are good.
|
| Hard to believe that was 26 years ago now. I think I was just on
| the verge of getting into Lisp.
|
| I see the sfx.c code assumes it would never deal with negative
| character values, so it cheerfully uses the <ctype.h> functions
| without a cast to unsigned char. It's a reasonable assumption
| there since the inputs under the intended use case would be
| expressions in the user's program, stringified by the
| preprocessor. Funny bytes would only occur in a multi-byte string
| literal (e.g. UTF-8). When I review code today, this kind of
| potential issue immediately stands out.
|
| The same exception module is (still?) used in the
| Ethereal/Wireshark packet capture and analysis tool. It's used to
| abort "dissecting" packets that are corrupt or truncated.
| utopcell wrote:
| C[++] never ceases to amaze me. Just yesterday I saw this,
| totally valid, code snippet:
|
| void g(); void f() { return g(); }
| gpderetta wrote:
| What's wrong with that?
| gizmo686 wrote:
| In C function prototypes with no arguments are strange.
| void g();
|
| Means that g is a function which takes a not-specified number
| of arguments (but not a variable number of arguments).
|
| Almost always what you want to do is void
| g(void)
|
| Which says that g takes 0 arguments.
|
| Having said that, declaring it as g() should work fine as
| long as you always provided that you always invoke it with
| the correct arguments. If you try invoking it with the wrong
| arguments, then the compiler will let you, and your program
| may just break in fun and exciting ways.
|
| Edit: looking closer, it looks like the intent might have
| been to alias f and g. But, as discussed above, it does so in
| a way that will break horribly if g expects any arguments.
| utopcell wrote:
| Well, at the risk of stating the obvious: `return expr;` is
| meant to return whatever expr evaluates to.
|
| Here, g()'s return type is void, so there's no value to
| return and at the same time, f()'s return type is void, so
| return should not have an expr to begin with.
|
| This statement is effectively equivalent to: "g(); return;".
| frizlab wrote:
| so?
| josephg wrote:
| > so return should not have an expr to begin with.
|
| Thats one way to think about it. Another is that void is a
| type - which is obviously true given you can have void*
| pointers and functions can return void. In this example,
| f() returns a void expression, so that's a perfectly fine
| thing to return from g.
| utopcell wrote:
| void* is leaps-and-bounds different from void. It is an
| expression, it has storage, you can actually assign
| something to it. You can't have a variable of type void;
| "void var;" is meaningless. A function doesn't "return"
| void. void simply denotes that a function doesn't return
| anything.
| int_19h wrote:
| You can't have a variable of type void, but you can have
| an expression of type void - calling a function that
| returns void does just that (in C++, "throw" is also a
| void-typed expression).
| Narishma wrote:
| Not the parent but I too don't see what's wrong with that.
| dzaima wrote:
| [delayed]
| jwilk wrote:
| First part discussed on HN:
|
| https://news.ycombinator.com/item?id=40835274 (113 comments)
| psychoslave wrote:
| Usually I'm more in the camp of "let's preserve everything we can
| as cultural heritage, yes even those awful Nazi propaganda
| material" and I'm confident that some distant archeologist (or
| current close neighbor) will be glad we did.
|
| But as time pass, I'm more and more convinced that wiping-out
| every peace of C that was ever produced would be one of the
| greatest possible gesture for the future of humanity.
|
| I also have a theory that universe exits so we can have some
| opportunities to taste chocolate. Surely in that perspective,
| even C can be an unfortunate but acceptable byproduct.
| adonovan wrote:
| Remember that C's contemporary languages were either
| inefficient (e.g. ALGOL 68, PL/1, Lisp), functionally obsolete
| (e.g. FORTRAN didn't have recursion or heap allocation), or
| even lower level (Assembly, B). C eliminated the need for need
| for assembly in programs that were low level (like OS kernels)
| or high performance (math, graphics, signal processing), and
| that was surely a huge improvement in type safety and
| expressiveness.
| chasil wrote:
| But how interesting would your life become if SQLite ceased to
| function anywhere around you?
|
| Should this misfortune befall you, please don't get on an
| airplane (with me).
| H8crilA wrote:
| Too many people still don't understand C's greatest failure:
| the undefined behavior. Most people assume that if you write
| past an array then the result may be a program crash; but
| actually undefined behavior includes other wonderful options
| such as stealing all your money, encrypting all your files and
| extorting you for some bitcoin, or even partially destroying a
| nuclear isotopes processing facility. Undefined really means
| undefined, theoretically some demons may start flying out of
| your nose and it would be completely up to spec. If you think
| that this is justified by "performance gains" or some other
| nonsense then I really don't know what to tell you!
| uecker wrote:
| I don't get that C hate. That terse syntax can be misused to
| produce unreadable code in C, does not change that I usually
| find it more readable than more verbose syntax.
| mystified5016 wrote:
| Forward parameter declaration is an insane feature. It makes
| perfect sense in the context of C's other forward declarations
| but just bonkers.
|
| I can't wait to slip this into some production code to confuse
| the hell out of some intern in a few years
| AKluge wrote:
| An old classic: Remember, C was never intended to be taken
| seriously. https://www.cs.cmu.edu/~jbruce/humor/unix_hoax.html
___________________________________________________________________
(page generated 2025-01-10 23:00 UTC)