7 ms·
Cello High Level C: A Fat Pointer Library
- tomp 11y agoIf I understand this correctly, this are not fat pointers as used in e.g. Rust, where instead of a pointer you'd be passing in an argument a struct with two pointers, one to the object and the other to the implementation of the interface/typeclass. Instead, they're more akin to vtables in C++, i.e. placing a pointer to the interface at the beginning of the object, before the data. I need to read the source (or generated code) to fully understand how they implemented support for multiple interfaces/typeclasses.
- ecma 11y agoI think you might be misunderstanding the intentions of this. AFAICT it's not a mechanism for supporting multiple interfaces, just a bit of a light trickery to get a length passed into functions within the limitations of array->pointer decay in C. It also helps avoid cases where a miscalculated n is passed by 'encapsulating' it with the data. Edit: I stand corrected. Just had a look over the source and this does a lot more than the OP link indicates. The Github README is more informative.
- TazeTSchnitzel 11y agoThese aren't fat pointers. A fat pointer is, well, fat: it's twice the size of a normal pointer, because it has both a memory address and a length. But Cello's 'fat pointers' are just regular pointers that happen to point to memory preceded by a length. Cello's 'fat pointers' don't solve the array slice problem.
- jasonwatkinspdx 11y agoYeah, and the two ideas can be combined as well, with fat pointers that are base pointer and offset/index pairs.
- deleted 11y ago[deleted]
- huhtenberg 11y agoA pointer you get from calloc() is guaranteed to be properly aligned to not cause SIGBUS on RISCs and some such. You cannot just add random sizeof(Header) to it and call it a general purpose allocation. struct Header needs to be a bit more elaborate than what they have. In fact, it shouldn't be a struct, but a union - union Header { var type; max_align_t foo; }; whereby you get max_align_t from your standard libraries or do something like this - typedef union max_align { int i; long l; double d; void * p; ... } max_align_t;
- ecma 11y ago+1 - I was hunting for this to make the same point. Your Header is exactly how I would expect it to be done.
- tomp 11y agoAre you sure this is not wasteful? I always thought you could align on any word (32 bits) on a 32-bit x86 CPU, but your max_align_t would be 64 bits long (because it includes a double as well).
- huhtenberg 11y agoThat's why you should try and use standard (or compiler-specific) version of max_align_t.
- danieltillett 11y agomax_align_t is only defined in C11? Edit. Yes this looks to be the case for those of us using C99. Paul Eggert put in a patch for gnulib to cover this [1]. typedef union { char *__p; double __d; long double __ld; long int __i; } max_align_t; One thing I did learn looking through this patch was the difference NULL has in C++ and C. This is certainly not something I had ever considered. 1. https://lists.gnu.org/archive/html/bug-gnulib/2014-12/msg00170.html https://lists.gnu.org/archive/html/bug-gnulib/2014-12/msg001...
- DSMan195276 11y ago
- kabdib 11y agoIt's all fun and games until you say ++p and pass that to a function that is silently expecting a length and other stuff in front of it. Calling these things "pointers" is a bit of a promotion from what they really are. Naming them "char*" or whatever is pretty dangerous if you ever do pointer movement. Seriously, if you want descriptors and typed blobs, just make up a struct for what you want and pass it around. Say what you mean. Lying like this is going to hurt you.
- mpu 11y agoThat's what Cello is about I think.
- tptacek 11y agoThis is also a common, and sometimes exploitable, coding error with C++ smart pointers. I agree with you: I'm not sure how this is any more powerful than any encapsulated ADT-style foo_t-wraps-a-void-star C library.
- Buge 11y agoWell with C++ unique_ptr and shared_ptr you cant ++ or += or even get a raw pointer without explicitly calling .get().
- mrfusion 11y agoWhat's the ELI5 here?
- codezero 11y agoPut size information before the pointer in memory.
- asimjalis 11y agoNeat punning on C pointers with minimal increase in memory-footprint. Some people have suggested putting array sizes in structs. But that will increase the level of indirection and hurt performance. Also it will require rewriting most applications. With Cello's approach you just have to avoid pointer arithmetic. Everything should work.
- scaramanga 11y agoOnly if you pass a pointer to that struct. But you can just pass it by-val and it will act the same way as 2 arguments.
- nocsaer1 11y agoDon't get me started... It is called a struct. It holds arbitrary collection of any type of data safely while enabling the lazy programmer to pass only one parameter around. It is also safe (as must as it can be in C) unlike pointers that hide data behind their back and are just asking for trouble. I will ignore all the problems and point out the worst offender, stack allocation example. That is not how you allocate arbitrary data on the stack, automatic char array is not malloc. In fact, in C you can't do it without using special platform specific functions. Anything else will cause undefined behavior.
- ploxiln 11y agoVariable length arrays on the stack are in the C99 standard. (If you're using Microsoft stuff, you have my condolences.)
- nocsaer1 11y agoMy argument doesn't rely on their (non) availability.
- mistercow 11y agoI think the reason for this is that they're trying to manipulate C's syntax to get a certain kind of intuitive style. They seem to want it to be concise and C-like (without resorting to macros), but they want to add their own functionality without actually extending the language. In this case, they want you to just be able to cast a Cello "var" pointer to a specific type pointer using the normal cast syntax. I'm not saying it's the right choice. Just that I understand why they did it that way. Still, I think a macro would be a perfectly reasonable way to do it.
- Arnavion 11y agoThis is the same as Pascal strings, popular among Windows programmers in the form of the BSTR type. BSTR also uses the same trick of putting the length before the string data so that the BSTR pointer can be treated as a char pointer.
- mpu 11y agoAnybody who wrote a medium-sized project in C is aware of such a trick. And also of the load of problems that comes with it! (They are not really pointers anymore.) Please stop trying to make C look like javascript, just create a new language from scratch, the pile of syntax tricks you're playing with is bound to collapse.
- yason 11y agoA subset of this kind of tricks are usually embedded in the source code of any project of decent size or larger. But that sort of implementation usually comes with big disclaimers that spell out the hacky parts in very loud terms. I'm kind of wary of a library trying to glue new behaviour onto C on a general level. There are inevitably too many corner-cases which break the newly built abstraction. Those corner-cases can be explained away for one specific codebase that already comes with its own set of practices, rules of thumb, and coding patterns that the programmers are already forced to learn. But a library as general as this is easily used liberally, even casually, and without a complete map of the shortcomings and broken corners I can see lots of potential hair-pulling coming right up when things are no longer what they seem they should be.
- Buge 11y agoFor alloc it says head->type = type; Where does type come from?
- asveikau 11y agoThere is all kinds of standards-abhorrent stuff and bad practices in here. Examples from the link: struct Header* head = calloc(1, sizeof(struct Header) + data_size); head->type = type; Head is not checked for null. This will segfault when heap allocations fail. [Preemptive reply to folks used to high-level languages and the Linux overcommit behavior: yes, heap allocations do fail, and yes, it is possible to handle such a failure well.] typedef void* var; // ... return ((var)head) + sizeof(struct Header); It's illegal to do pointer arithmetic on void pointers. You need to cast to another type first. #define alloc_stack(T) header_init( \ (char[sizeof(struct Header) + sizeof(struct T)]){0}, T) That's going to crash when you try to access the T on many non-x86 architectures. On x86 you will have subtle problems like atomic ops failing to work. I have seen cello come up on HN before. Seems like a cute hobby project based on some kind of flawed ideas of what "C" is. If people interested in learning C are reading, I suggest learning C "for realsies" and avoiding this thing.
- kabdib 11y agoFully agreed about "what C is". Many, many sins have been committed in the name of saving keystrokes or making things temporarily easier. That little voice in the back of your head telling you that something might be a bad idea? Listen to it, it's probably right. On handling out-of-memory conditions: - Yes, you can do it - It's also hard to get right, in the general case, for large systems. It's often a pyrrhic victory Most systems I've worked with have simply restarted, rather than risk getting complicated recovery logic wrong (and winding up in a worse situation -- corrupting persistent data, or giving wrong answers -- than if they simply crashed). A few systems have a 'reserve tank' that they can use to do a controlled crash, saving important state and whatnot before quitting. OOM can get pretty wicked.
- asveikau 11y agoThere are large projects that handle OOM and it isn't that hard to do. Imagine every function in your call stack handles errors consistently. In most cases these functions will bubble up all errors to the caller. In many cases they will perform allocations themselves and free those allocations when they fall out of scope due to either success or error. The function at the top of the stack hits a malloc error. It will bubble up its status to its caller, who will do the same for his caller, etc. The chain of functions will free intermediate allocations they made along the way. By the time you get to some top-level or near-top-level function, you can react to the error, and you likely even have quite a bit of heap space when the rest of the stack frees its work. But if you don't happen to have the heap space, then you can structure that top-level error handler so that it performs very few allocations, does allocations at upfront at initialization time, etc. I don't think any of this is hard and I've seen it work well in practice. It's sad to me when I see the opposite, some unreasonable allocation quite reasonably fails, and it takes down the entire process because whoever wrote that code thought it was too hard to do otherwise.
- sagargv 11y agoI like the idea of breathing more life into C. Look at what's happened to C++. These days, when I need the efficiency, I prefer coding in C to C++. But I'm not sure Cello is the right way to make C better. If you're starting a new project, what's wrong with a struct that has an array and length of the array? Cello seems to break the simplicity of C and its memory model.