• Used-Defined Builtins

    From Lawrence D'Oliveiro@21:1/5 to All on Fri Feb 9 06:05:35 2024
    Modula-2 had this interesting feature where, for example if you had a
    pointer variable whose pointed-to objects were of type T

    VAR
    ptr : POINTER TO T;

    and you had a statement like

    ptr := NEW(T);

    then the compiler would translate the “NEW(T)” into “ALLOCATE(TSIZE(T))”,
    where the “ALLOCATE” function was not actually provided by the language, but had to be defined/introduced in the current scope somehow (perhaps
    IMPORTed from some implementation-provided library).

    This allowed for memory allocations (and also deallocations) to be
    expressed in a type-safe fashion, while still leaving the low-level
    details of memory management up to some library/user code that was not an integral part of the language implementation.

    So you had a builtin function, NEW(), that could take a type as an
    argument and return a result of the appropriate pointer type, which a
    normal user-defined function could not do, but the compiler would delegate
    the main work to a lower-level function that didn’t need to know anything about the language type system, it only needed to know how much memory to allocate, and would return an untyped pointer, which the compiler would
    then take care of turning into a pointer of the required type.

    C could benefit from some similar high-level+low-level layering like this, don’t you think?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Lawrence D'Oliveiro on Fri Feb 9 08:08:32 2024
    On 2024-02-09, Lawrence D'Oliveiro <ldo@nz.invalid> wrote:
    Modula-2 had this interesting feature where, for example if you had a
    pointer variable whose pointed-to objects were of type T

    VAR
    ptr : POINTER TO T;

    and you had a statement like

    ptr := NEW(T);

    [ ... ]

    C could benefit from some similar high-level+low-level layering like this, don’t you think?

    Yes, someone thought that (and other things) and came up with C++.

    T *p = new T;

    If T is a class, it can have its own allocator. The new operator
    ensures that the constructor is called to initialize the T object.
    You can pass constructor parameters to choose different constructor
    overloads:

    T *p = new T(4, "blah");

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Kaz Kylheku on Fri Feb 9 09:17:15 2024
    On 09/02/2024 09:08, Kaz Kylheku wrote:
    On 2024-02-09, Lawrence D'Oliveiro <ldo@nz.invalid> wrote:
    Modula-2 had this interesting feature where, for example if you had a
    pointer variable whose pointed-to objects were of type T

    VAR
    ptr : POINTER TO T;

    and you had a statement like

    ptr := NEW(T);

    [ ... ]

    C could benefit from some similar high-level+low-level layering like this, >> don’t you think?

    Yes, someone thought that (and other things) and came up with C++.

    T *p = new T;

    If T is a class, it can have its own allocator. The new operator
    ensures that the constructor is called to initialize the T object.
    You can pass constructor parameters to choose different constructor overloads:

    T *p = new T(4, "blah");


    You can also provide a new global "new" operator if you want.

    Making a "safer" replacement, alternative or enhancement to C was one of
    the motivations for C++. Perhaps unfortunately, you can write C++ as
    unsafely as you want - but if you decide to put in the effort it has the features to make your code a great deal safer than plain old C.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From bart@21:1/5 to Lawrence D'Oliveiro on Fri Feb 9 15:36:02 2024
    On 09/02/2024 06:05, Lawrence D'Oliveiro wrote:
    Modula-2 had this interesting feature where, for example if you had a
    pointer variable whose pointed-to objects were of type T

    VAR
    ptr : POINTER TO T;

    and you had a statement like

    ptr := NEW(T);

    then the compiler would translate the “NEW(T)” into “ALLOCATE(TSIZE(T))”,
    where the “ALLOCATE” function was not actually provided by the language, but had to be defined/introduced in the current scope somehow (perhaps IMPORTed from some implementation-provided library).

    This allowed for memory allocations (and also deallocations) to be
    expressed in a type-safe fashion, while still leaving the low-level
    details of memory management up to some library/user code that was not an integral part of the language implementation.

    So you had a builtin function, NEW(), that could take a type as an
    argument and return a result of the appropriate pointer type, which a
    normal user-defined function could not do, but the compiler would delegate the main work to a lower-level function that didn’t need to know anything about the language type system, it only needed to know how much memory to allocate, and would return an untyped pointer, which the compiler would
    then take care of turning into a pointer of the required type.

    C could benefit from some similar high-level+low-level layering like this, don’t you think?

    It's not particularly high level if all it does is allocate a possibly uninitialised block of memory of a suitable size.

    You will probably still need to deallocate manually or rely on a GC.
    (Can you do F(NEW(T), NEW(T), NEW(T)?)

    Where T has a variable size attached to it, for example ARRAY OF U, then
    NEW will need to know the size, but also, this needs to be somehow
    associated with that instance of T.

    (And can U here itself be something you'd call NEW on?)

    It's hard to retrofit such features into a lower-level language. In C
    you can do:

    ptr = malloc(sizeof(*ptr))

    but it is not checked by the compiler. (I assume Modular 2 will complain
    if you do 'ptr := NEW(U)'.)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Thiago Adams on Fri Feb 9 17:29:14 2024
    On 09/02/2024 16:03, Thiago Adams wrote:

    This is C23 code


    #include <stdlib.h>
    #include <string.h>

    static inline void* allocate_and_copy(void* s, size_t n) {
        void* p = malloc(n);
        if (p) {
            memcpy(p, s, n);
        }
        return p;
    }

    #define NEW(...) (typeof(__VA_ARGS__)*)
    allocate_and_copy(&(__VA_ARGS__), sizeof(__VA_ARGS__))
    #pragma expand NEW

    struct X {
        const int i;
    };

    int main() {
        auto p = NEW((struct X) {});
    }


    "#pragma expand" is not standard C. What does it mean, and what tools
    support it?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Janis Papanagnou@21:1/5 to Lawrence D'Oliveiro on Mon Feb 12 03:31:52 2024
    On 09.02.2024 07:05, Lawrence D'Oliveiro wrote:
    Modula-2 had this interesting feature where, for example if you had a
    pointer variable whose pointed-to objects were of type T

    VAR
    ptr : POINTER TO T;

    and you had a statement like

    ptr := NEW(T);

    then the compiler would translate the “NEW(T)” into “ALLOCATE(TSIZE(T))”,
    [...]

    The type bound pointer type was already introduced by Niklaus Wirth
    in Pascal (with an only slightly different syntax, type PT = ^T ),
    so it's not too surprising that he used that concept also in Modula.


    C could benefit from some similar high-level+low-level layering like this, don’t you think?

    Yes. Type bound pointers have been said (I think by F. L. Bauer)
    to be sort of a tamed/controlled version of an inherently insecure (pointer-)concept. I'm not sure, though, (and too tired to ponder
    about it) but I seem to recall that with that coupling there's the
    possibility to write type-safe pointer constructs; that would have
    (IIRC) to be supported by the compiler. So introducing it in C as a
    layer on top would probably not suffice to gain the same safety.
    Hiding only the allocation would obviously be just syntactic sugar.

    You find that binding also in C++ (as has been mentioned elsethread)
    but that had been borrowed from Simula: REF(T) p; p :- new T; Both
    languages allow to assign subtypes in a class hierarchy, of course,
    to make most sense.

    Janis

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)