Discussion:
Used-Defined Builtins
Add Reply
Lawrence D'Oliveiro
2024-02-09 06:05:35 UTC
Reply
Permalink
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?
Kaz Kylheku
2024-02-09 08:08:32 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
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);
[ ... ]
Post by Lawrence D'Oliveiro
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: @***@mstdn.ca
David Brown
2024-02-09 08:17:15 UTC
Reply
Permalink
Post by Kaz Kylheku
Post by Lawrence D'Oliveiro
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);
[ ... ]
Post by Lawrence D'Oliveiro
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
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.
Thiago Adams
2024-02-09 15:03:11 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
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?
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) {});
}



https://thradams.com/cake/playground.html?code=CiNpbmNsdWRlIDxzdGRsaWIuaD4KI2luY2x1ZGUgPHN0cmluZy5oPgoKc3RhdGljIGlubGluZSB2b2lkKiBhbGxvY2F0ZV9hbmRfY29weSh2b2lkKiBzLCBzaXplX3QgbikgewogICAgdm9pZCogcCA9IG1hbGxvYyhuKTsKICAgIGlmIChwKSB7CiAgICAgICAgbWVtY3B5KHAsIHMsIG4pOwogICAgfQogICAgcmV0dXJuIHA7Cn0KCiNkZWZpbmUgTkVXKC4uLikgKHR5cGVvZihfX1ZBX0FSR1NfXykqKSBhbGxvY2F0ZV9hbmRfY29weSgmKF9fVkFfQVJHU19fKSwgc2l6ZW9mKF9fVkFfQVJHU19fKSkKI3ByYWdtYSBleHBhbmQgTkVXCgpzdHJ1Y3QgWCB7CiAgICBjb25zdCBpbnQgaTsKfTsKCmludCBtYWluKCkgeyAKICAgIGF1dG8gcCA9IE5FVygoc3RydWN0IFgpIHt9KTsgICAgIAp9Cg%3D%3D&to=-1&options=

https://godbolt.org/z/b7z8YndWb
Keith Thompson
2024-02-09 16:20:55 UTC
Reply
Permalink
Thiago Adams <***@gmail.com> writes:
[...]
Post by Thiago Adams
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) {});
}
The "#define NEW" line was wrapped, probably by your newsreader.
Either join the lines or add a backslash on the "#define" line.

"#pragma expand" is non-standard. I think it's specific to "cake", a
tool that translates C23 to earlier versions of C
(https://github.com/thradams/cake/).
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
Working, but not speaking, for Medtronic
void Void(void) { Void(); } /* The recursive call of the void */
Thiago Adams
2024-02-12 04:34:46 UTC
Reply
Permalink
Post by Keith Thompson
[...]
Post by Thiago Adams
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) {});
}
The "#define NEW" line was wrapped, probably by your newsreader.
Either join the lines or add a backslash on the "#define" line.
"#pragma expand" is non-standard. I think it's specific to "cake", a
tool that translates C23 to earlier versions of C
(https://github.com/thradams/cake/).
Exactly.
I forgot to remove pragma expand from the sample.
But it can be removed and the code still works.
David Brown
2024-02-09 16:29:14 UTC
Reply
Permalink
Post by Thiago Adams
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?
Thiago Adams
2024-02-12 04:37:19 UTC
Reply
Permalink
Post by Thiago Adams
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?
It can be removed in this sample. (It is not standard)

It is necessary in cake because cake is a transpiler and transformations
(typeof) inside the macro NEW need to be expanded at the generated code.


https://thradams.com/cake/playground.html?code=CiNpbmNsdWRlIDxzdGRsaWIuaD4KI2luY2x1ZGUgPHN0cmluZy5oPgoKc3RhdGljIGlubGluZSB2b2lkKiBhbGxvY2F0ZV9hbmRfY29weSh2b2lkKiBzLCBzaXplX3QgbikgewogICAgdm9pZCogcCA9IG1hbGxvYyhuKTsKICAgIGlmIChwKSB7CiAgICAgICAgbWVtY3B5KHAsIHMsIG4pOwogICAgfQogICAgcmV0dXJuIHA7Cn0KCiNkZWZpbmUgTkVXKC4uLikgKHR5cGVvZihfX1ZBX0FSR1NfXykqKSBhbGxvY2F0ZV9hbmRfY29weSgmKF9fVkFfQVJHU19fKSwgc2l6ZW9mKF9fVkFfQVJHU19fKSkKI3ByYWdtYSBleHBhbmQgTkVXCgpzdHJ1Y3QgWCB7CiAgICBjb25zdCBpbnQgaTsKfTsKCmludCBtYWluKCkgeyAKICAgIGF1dG8gcCA9IE5FVygoc3RydWN0IFgpIHt9KTsgICAgIAp9Cg%3D%3D&to=-1&options=
Thiago Adams
2024-02-12 04:44:30 UTC
Reply
Permalink
Post by Thiago Adams
Post by Lawrence D'Oliveiro
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?
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) {});
}
https://thradams.com/cake/playground.html?code=CiNpbmNsdWRlIDxzdGRsaWIuaD4KI2luY2x1ZGUgPHN0cmluZy5oPgoKc3RhdGljIGlubGluZSB2b2lkKiBhbGxvY2F0ZV9hbmRfY29weSh2b2lkKiBzLCBzaXplX3QgbikgewogICAgdm9pZCogcCA9IG1hbGxvYyhuKTsKICAgIGlmIChwKSB7CiAgICAgICAgbWVtY3B5KHAsIHMsIG4pOwogICAgfQogICAgcmV0dXJuIHA7Cn0KCiNkZWZpbmUgTkVXKC4uLikgKHR5cGVvZihfX1ZBX0FSR1NfXykqKSBhbGxvY2F0ZV9hbmRfY29weSgmKF9fVkFfQVJHU19fKSwgc2l6ZW9mKF9fVkFfQVJHU19fKSkKI3ByYWdtYSBleHBhbmQgTkVXCgpzdHJ1Y3QgWCB7CiAgICBjb25zdCBpbnQgaTsKfTsKCmludCBtYWluKCkgeyAKICAgIGF1dG8gcCA9IE5FVygoc3RydWN0IFgpIHt9KTsgICAgIAp9Cg%3D%3D&to=-1&options=
https://godbolt.org/z/b7z8YndWb
Initially in cake, new was an operator.
The reason it was removed is because I didn't find a good way of inform
the "allocator" function.

In C++ for instance, new is very complicated with several overrides.

The open problem is how to allocate an object in heap that have constant
members.

struct X {
const int type;
};

how to create struct X on heap?

this macro solves this problem.

I am not using this macro in production.(maybe because I am not using c23)
bart
2024-02-09 15:36:02 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
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)'.)
Janis Papanagnou
2024-02-12 02:31:52 UTC
Reply
Permalink
Post by Lawrence D'Oliveiro
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.
Post by Lawrence D'Oliveiro
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
Loading...