Hacker News new | past | comments | ask | show | jobs | submit | joejev's comments login

Interesting idea, but this implementation has UB:

    typedef void* var;

    struct Header {
      var type;
    };

    // ...

    #define alloc_stack(T) header_init( \
      (char[sizeof(struct Header) + sizeof(struct T)]){0}, T)

    var header_init(var head, var type) {
      struct Header* self = head;
      self->type = type;
      return ((char*)self) + sizeof(struct Header);
    }
The section "struct Header* self = head" is UB. The alignement requirement of the local char array is 1 but the alignment requirement of struct Header is that of void* which is probably 8.


That's just what I was wondering, are "magic" libraries like this still safe to use considering modern compiler's UB shenanigans?


Not only that but you have a pointer to a parameter returned back and used outside its scope ...


It's not a pointer to a parameter, it is just the parameter itself.

var is a typedef for void* and no & appears in the function.


> no & appears in the function.

It's an array, so you don't need & to take its address, it decays into a pointer without &.

Imagine:

    char buf[sizeof(struct Header) + sizeof(struct T)];
    char *p = buf;
Then take away the names so that you are effectively passing p as a parameter... Then returning p.

As in, an anonymous temporary being given to the function, and the function returns its address back.

It's assuming that this temporary parameter buffer will exist after it is used and the function has returned. I'm not sure what the standard says for that but it is crazy sketchy. [Edit: Googling around, it seems like maybe this is illegal in C99 but possibly legal in C11? Or that C11 changed the rules for this. Does not seem like a great thing to rely upon.]


There is no issue here (except the one highlighted by joejev).

Many standard library functions return a pointer which they got as a parameter (or another pointer offset from it, as is the case here). The compound literal is no more "temporary" than a variable that was introduced right before the function call. Lifetimes of compound literals are specified in section 6.5.2.5 of both C99 and C11.

  func((int[256]){ 0 });
  // is mostly equivalent to 
  int __a__[256] = { 0 };
  func(__a__);


> Many standard library functions return a pointer which they got as a parameter

Obviously. But this does not extend the lifetime of the buffer they are passed. Namely you can't use this as a technique to extend the life of automatic storage falling out of scope.

> Lifetimes of compound literals are specified in section 6.5.2.5 of both C99 and C11.

This is what I was missing. So it is valid by the standard. Which is good if you have looked it up. It remains not obvious when reading source without a copy of the standard on hand, or prior knowledge of that section. Passing an expression of that sort and keeping a pointer to it visually looks like the intent is to retain a pointer of more limited scope. Intuitively it would make just as much sense if the lifetime were shorter. If you are seeking clarity of intent this is not a great thing to rely upon.


It's not an ad-hoc turing complete language, it is just a programming language. Makefiles are just programs. There are some strange implicits, but that isn't unique to the Make programming language.


It's Turing complete because all standard programming languages are Turing complete (modulo some technicalities which are not relevant here).

It's ad-hoc because it was built gradually on top of the original Unix make, which supported rules and variables but not any of the more advanced features. This history explains many of its quirks and relatively extreme limitations as a programming language, such as:

- Expressions must be all on one line, except within `define` blocks or if you use backslashes to join lines together (which tends to result in a lot of backslashes).

- Hard tabs are required to introduce commands in rules but are (mostly) banned outside of them, so if you want any kind of indentation for an if block or user-defined function, you need to use spaces instead.

- User-defined functions. Calling them is merely more verbose than calling builtin functions (you need to invoke the `call` builtin function). Defining them is worse: function parameters are numbered rather than named, similar to a shell, but in a shell you can reassign them to named variables, whereas Make's expansion rules make that awkward to achieve.

- And of course, no types other than strings (a limitation partly shared by shells, but most shells do have arrays, even if they're awkward to use).

Admittedly, most of that gradual evolution happened a long time ago, and GNU Make has been relatively stable as a language since then. It's not "ad-hoc" in the sense that it's constantly changing or ill-defined. But that stability also means that it never outgrew the limitations of its original design.

Some research I did for fun:

The oldest version of GNU Make I can find [1], from 1988, already had a handful of functions, including `foreach`, `filter`, `patsubst`, etc., as well as support for multi-line definitions (`define`/`endef`). `call`, on the other hand, didn't appear until 1999. Amusingly, its initial implementation came with a comment [2] bemoaning the lack of "nested lists and quotes", and even semi-seriously proposing that GNU Guile (Lisp implementation) be integrated into Make, in order to let Makefile authors use a 'real' programming language. No fewer than 13 years later, that proposal actually became a reality; unfortunately, it was an optional feature which distributions tended to leave disabled, ergo Makefile authors could not rely on it being present, so the feature has seen approximately zero use.

[1] within https://gcc.gnu.org/pub/binutils/old-releases/binutils-1988-...

[2] https://github.com/mirror/make/blob/c4353af3f9b143b213644c09...


I think they meant that the bytecode itself reads like a forth. The data stack works just like it does in forth within a single Python function.


> The argument comes down to when the undefined behavior occurs: is it at the deference to create the reference, or is it on the first memory access using the reference? The language pedants will say the former, but in practice, it's the latter.

The undefined behavior is always when the null reference is created. The issue will _usually_ manifest when you try to dereference the pointer, but the undefined behavior was creating the null reference in the first place.


> And regardless of that, if my employer is willing to pay our CEO over 20x what they pay their average employee, they are not in a position to niggle over the kinds of systemic inefficiencies they would suffer under a union.

Do you not believe that a CEO could have at least 20 times the impact on the value of a company than the average employee, and if so, should they not be compensated accordingly?


No, I do not believe in the divine right of kings.


I work on a library for doing Python bytecode transformations like this, but with a more abstract API. Here is a similar transformation with this library, which works with Python 3: https://github.com/llllllllll/codetransformer/blob/master/co...


So, like some sort of shared library that my programs dynamically communicate with? How is this functionally different from a shared object?


> How is this functionally different from a shared object?

Because it's a separate process, so when it crashes your application can put up a "SSL failed, reconnecting" message and carry on. Also when your application has read-something-somewhere security vulnerability, it can't compromise your SSL keys.

Nitpick: it's not one process, but one executable; you can have multiple SSL daemons using the same (read-only) binary, so a attack on one only gets one set of keys. (The same attack will probably work on every instance of the same version of SSLd, but shared objects don't fix that.)


It isn't really. The actual big problem with shared libraries is that developers cannot be trusted not to change their interfaces or behavior and break things. That's true regardless of what mechanism you use to share code.


I've been to chiles, not surprised to see that on the top of the list.


You need to know how the character is written regardless because the stroke order is part of the character. There are some basic rules like working left to right and top to bottom. Also, each component has the same order when written out.


I also seem to recall there's been successful input system like this for Japanese, in particular for handhelds with pen input (think psion and similar early devices).

Optical recognition of Kanji can be though, but with stroke direction it is easier.

See for example : https://jisho.org/#handwriting


Yes, but reading and writing are different skills. For example, there are some characters I can recognise but won't be able to write correctly.


What about a 9 hour flight? Bring 2 sets of pilots?


Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: