News for hackers. Stuff that matters.
Re:Show HN by cassepipe on Saturday December 03, 2022 @08:53AM (#33843459)
I always use antirez's (Redis creator) `sds` and advertise it whenvever I get the chance. Thanks to the someone who recommended it on HN some years ago. It's a joy to use.

https://github.com/antirez/sds

The trick is the size is hidden before the adress of the buffer.("Learn this one simple trick that will change your life for ever").

From the Readme:

```

Advantage #1: you can pass SDS strings to functions designed for C functions without accessing a struct member or calling a function

Advantage #2: accessing individual chars is straightforward.

Advantage #3: single allocation has better cache locality. Usually when you access a string created by a string library using a structure, you have two different allocations for the structure representing the string, and the actual buffer holding the string. Over the time the buffer is reallocated, and it is likely that it ends in a totally different part of memory compared to the structure itself. Since modern programs performances are often dominated by cache misses, SDS may perform better in many workloads.

```

Re:Show HN by _448 on Saturday December 03, 2022 @09:22AM (#33843667)
> The trick is the size is hidden before the adress of the buffer.

That is how strings use to be stored before C made the choice of using null-terminator. Pascal stored the string size before the string data. The advantage of relying on a terminator symbol is that the string size can be any length where as storing the size at the start forces the string to not exceed certain size.

Re:Show HN by anonymoushn on Saturday December 03, 2022 @09:31AM (#33843726)
In execution environments with 64-bit pointers you may have trouble loading a string of more than 16 exabytes into RAM anyway
Re:Show HN by mh7 on Saturday December 03, 2022 @09:34AM (#33843745)
strlen() returns a size_t so you're already constrained to a maximum length of SIZE_MAX.
Re:Show HN by jcelerier on Saturday December 03, 2022 @09:42AM (#33843798)
If you use a different data structure you would maybe use a different API for accessing it too
Re:Show HN by jstimpfle on Saturday December 03, 2022 @10:19AM (#33844082)
This is hilarious. SIZE_MAX is at least as large as the largest string that you can put in your address space anyway. Which is what the strlen() API already assumes.

That, plus you'd be a fool to store a huge string in this way in any case.

Re:Show HN by Someone on Saturday December 03, 2022 @11:16AM (#33844505)
> SIZE_MAX is at least as large as the largest string that you can put in your address space / memory anyway.

Not necessarily. A 64-bit system could give processes an address space that’s significantly larger than half the full 64-bit address space and have an allocator that allows you to allocate a block of more than SIZE_MAX bytes (malloc takes a size_t, but you can use calloc)

Re:Show HN by jstimpfle on Saturday December 03, 2022 @11:35AM (#33844696)
This doesn't make sense to me. You can't "allocate" more than SIZE_MAX bytes by definition. If you take "allocate" to mean "make it available in the process's address space", that is.
Re:Show HN by unwind on Saturday December 03, 2022 @03:36PM (#33847052)
Are you sure?

The calloc() [1] function mentioned above takes two values of type size_t, and allocates their product bytes.

I'm on mobile without (!) the C99 draft spec but at least the man page gives no such restriction.

[1] https://linux.die.net/man/3/calloc

Re:Show HN by mek6800d2 on Saturday December 03, 2022 @04:38PM (#33847649)
I read something about this recently, somewhere, maybe HN. Specifically, in calloc(), what is done and what should really be done if the multiplication overflows. As will happen, for example, if you try to calloc() two elements of size SIZE_MAX, when SIZE_MAX is the maximum representable unsigned integer value on the machine. So, I don't think calloc() is available or intended as a way to circumvent malloc()'s size restriction.
Re:Show HN by Someone on Saturday December 03, 2022 @06:39PM (#33848716)
I stand corrected. Initially, I thought that, even if it calloc can’t, an OS could provide a different way to obtain a pointer to a memory region that’s larger than SIZE_MAX.

However, the standard says (https://en.cppreference.com/w/c/types/size_t):

“size_t can store the maximum size of a theoretically possible object of any type (including array).”

and (https://en.cppreference.com/w/c/language/pointer):

“Pointer is a type of an object that refers to a function or an object of another type, possibly adding qualifiers. Pointer may also refer to nothing, which is indicated by the special null pointer value.”

⇒ pointers must either be null or point to an object, and objects aren’t larger than SIZE_MAX, so I think having a pointer pointing to a block larger than SIZE_MAX violates the standard.

Re:Show HN by jstimpfle on Saturday December 03, 2022 @06:51PM (#33848836)
> pointer pointing to a block larger than SIZE_MAX violates the standard.

it's simply not possible, by definition.

Re:Show HN by jstimpfle on Saturday December 03, 2022 @06:41PM (#33848732)
I mean, explain first how you plan to allocate more address space than is addressable?

calloc returns NULL when it's not possible to satisfy the request.

Re:Show HN by ahepp on Saturday December 03, 2022 @01:11PM (#33845747)
Isn't size_t defined as being able to fit the largest possible data allocation?
Re:Show HN by Karellen on Saturday December 03, 2022 @01:56PM (#33846196)
size_t is unsigned, right? ssize_t is the signed version?

On a quick test on my 64-bit system, a C program doing `printf("%zu\n", SIZE_MAX);` outputs 18446744073709551615, which looks like (2^64)-1 to me.

Or is there a thing in the standard that says this isn't always the case?

Re:Show HN by arcticbull on Saturday December 03, 2022 @04:38PM (#33847657)
ssize_t is a weird one, the only negative value it is guaranteed to store is -1.

> The type ssize_t shall be capable of storing values at least in the range [-1, {SSIZE_MAX}].

[1] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sy...

Re:Show HN by jstimpfle on Saturday December 03, 2022 @08:22PM (#33849525)
size_t need only be large enough to cover the (virtual) address space. It's up to hardware and OS to decide how much addressable space you get. I believe current systems can use only the low 48 bits of 64-bit pointers. However that number is likely to be increased in the future and OSes would be unwise to define size_t as something smaller than 64 bits.
Re:Show HN by mananaysiempre on Tuesday December 06, 2022 @06:15AM (#33878114)
No, ssize_t is not the signed version. As best as I can tell, the only things POSIX says about ssize_t is that[1] it is an integer type that can hold integer values in [-1, SSIZE_MAX], where[2] SSIZE_MAX ≥ _POSIX_SSIZE_MAX = 32767, not that it should have any particular relation to size_t. In the standard, it is used for byte counts in I/O, like the return value of read() (traditionally int), for the return value of strfmon() and strfmon_l() (OK I guess, though the C standard stuck with int for *printf()), and for the argument to swab() (wat).

Note that neither is ptrdiff_t guaranteed to be that signed version, or to hold any possible value in the domain of size_t or (strictly speaking) any possible object size. Both GCC and Clang assume the latter, though, and can miscompile[3] code that relies on (e.g.) malloc() succeeding for sizes > 2^31 on a 32-bit system.

[1] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sy...

[2] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/li...

[3] https://trust-in-soft.com/blog/2016/05/20/objects-larger-tha...

Re:Show HN by pjmlp on Saturday December 03, 2022 @01:09PM (#33845726)
Indeed, you just need to forget to put a terminator to get a nice memory dump.
Re:Show HN by aap_ on Saturday December 03, 2022 @09:50AM (#33843863)
Null-termination was not a C invention.
Re:Show HN by thaumasiotes on Saturday December 03, 2022 @10:02AM (#33843960)
> The advantage of relying on a terminator symbol is that the string size can be any length where as storing the size at the start forces the string to not exceed certain size.

In the same way that since we identify unicode code points with a 16-bit value, it's impossible to include U+1D460 in a string?

In the same way that since Matroska files encode the length of their segments, there's a hard upper limit on the length of a segment?

Of course none of those things is actually true. Storing the string size has no implications for how long the string can be. It requires an amount of space, to store the string size, that is logarithmic in the length of the string, and completely insignificant.

Re:Show HN by lelanthran on Saturday December 03, 2022 @10:34AM (#33844217)
> In the same way that since we identify unicode code points with a 16-bit value

Not a single 16-bit value. Some codepoints are two 16-bit values.

Re:Show HN by kevin_thibedeau on Saturday December 03, 2022 @12:43PM (#33845386)
Codepoints are 21-bit values. They may have a more compact encoding but the unencoded form is fixed.
Re:Show HN by jstimpfle on Saturday December 03, 2022 @10:26AM (#33844134)
For sake of simplicity, and for efficiency with really small strings, with a length-prefixed string representation you really want to keep the string length field fixed-size. In general.
Re:Show HN by thaumasiotes on Saturday December 03, 2022 @11:05AM (#33844435)
Really small strings have a fixed-size length field in any variable-size encoding of the length. They're small, so they fit into whatever the smallest possible length field is.

What do you gain in handling short strings from an inability to handle long ones?

Re:Show HN by jstimpfle on Saturday December 03, 2022 @11:26AM (#33844588)
Ok I give you this one, but I still don't think that minimizing the size of a length field using a flexible width encoding is a good idea except when talking about extremely specialized string encodings (like compression schemes).

Flexible width encoding is more complicated compared to simple member access to get at the first character. And how do you handle construction of a string whose size you don't know yet? You might have to move the string away to make space for a bigger string length field. I don't like it.

Re:Show HN by thaumasiotes on Saturday December 03, 2022 @11:58AM (#33844908)
> Flexible width encoding is more complicated compared to simple member access to get at the first character.

I don't think this is true either. It's almost true. But what happens if the string length is 0?

If you make the assumption that you can access the first character of a zero-length string by just grabbing whatever is in memory after the string header, you're going to make the exact mistake the length field is there to stop you from making, a memory access violation. You have to process the length field in order to do any access at all; many strings don't have a first character.

> And how do you handle construction of a string whose size you don't know yet? You might have to move the string away to make space for a bigger string length field.

That's true; you'll either need to be willing to store the character data and the length metadata in separate locations, or you'll need to be willing to occasionally move the data around.

Re:Show HN by jstimpfle on Saturday December 03, 2022 @12:17PM (#33845123)
Obviously I mean get at the address of the first character, if any. You can't load before you know that what you load is valid. Btw. zero-terminated strings allow you to load unconditionally. Sometimes that's nice.
Re:Show HN by thaumasiotes on Saturday December 03, 2022 @12:30PM (#33845246)
OK, but now the difference in how complicated it is to read from the string boils down to this:

    1. Read the first chunk of the string length.
    2. Is it more than 0?
vs

    1. Read the first chunk of the string length.
    2. Did we get the whole thing?
    3. Is the length more than 0?
That extra step in the variable-length case means checking whether a bit is set in the value you just read.

---

Also, it occurs to me that this whole discussion is talking about how to serialize or deserialize a string, when the original discussion is over how the string should be represented in memory.

Re:Show HN by jstimpfle on Saturday December 03, 2022 @10:24AM (#33844125)
The biggest advantage of zero-terminated to me is simplicity, next would be efficiency for really small strings - although this is a fringe concern. Strings with explicit length should at least have a 32-bit length field (maybe 64) IMO - for example, it's common to read files (and store them in contiguous memory) that are larger than 64K.
Re:Show HN by jbverschoor on Saturday December 03, 2022 @11:01AM (#33844407)
Well you could easily use the first 4 bits to indicate how many bytes the length is + 1.

      c0 (0b0000) -> length is 0xc = 12
    1341 (0b0001) -> length is 0x134 = 69,940
  239a42 (0b0010) -> length is 0x239a42 = 145,828

  deadbeaf239a47 (0b0111) -> length is 0xdeadbeaf239a4 = 3917404957718948.
This gets you a 7-byte = 56bit number, minimal overhead for smaller strings.

Maybe reserve 0x1111 for future use.

Maybe the other endian makes more sense here, and maybe 0 should mean zero-length.

It's probably not very performant compared to other solutions, but you can just shift 4 bits, and you're done

I'm curious how many strings are allocated at a particular point in time (across everything, kernel, os, apps, etc)

Re:Show HN by jstimpfle on Saturday December 03, 2022 @11:11AM (#33844479)
These considerations can make sense when thinking about storage formats (probably you want to compress the string too), but they are not convenient for in-memory representation where you want to get the location of the first character with a simple member access.
Re:Show HN by jbverschoor on Saturday December 03, 2022 @11:24AM (#33844570)
It starts at the start of the frame + the first nibble + 1
Re:Show HN by abcd_f on Saturday December 03, 2022 @11:20AM (#33844542)
The length can be packed, e.g. like utf-8 does it or something similar. The caveat is the cost of unpacking on access, but the memory overhead will be minimal.
Re:Show HN by lifthrasiir on Saturday December 03, 2022 @12:18PM (#33845130)
Most memory allocators have an internal fragmentation which removes most efficiency gained by zero-termination. In fact it's worse, because zero-termination means that deallocation can't take a size parameter and it can often cause a performance hit for many modern allocators due to cache misses [1].

[1] https://isocpp.org/files/papers/n3778.html

Re:Show HN by jstimpfle on Saturday December 03, 2022 @06:39PM (#33848720)
zero-terminated says nothing about allocation, so there's that.
Re:Show HN by lifthrasiir on Saturday December 03, 2022 @07:14PM (#33849000)
You were talking about [space] efficiency for small strings, which is inherently related to the memory allocation scheme.
Re:Show HN by jstimpfle on Saturday December 03, 2022 @08:01PM (#33849357)
What? You can allocate zero-terminated strings in .rodata by typing "a string literal" in source code, for example. It's fair to say that there's no fragmentation or whatever and you don't have to think about deallocation either.

Other popular approaches to allocate strings dynamically would generally group strings of similar lifetime or by size, amortizing possible overheads over many strings.

If your favorite memory allocator requires a size field to support efficient deallocation, then by all means put that in the allocation record. But this is totally orthogonal to the format of a simple string representation.

Re:Show HN by thom on Saturday December 03, 2022 @01:19PM (#33845836)
SDS supports 64-bit lengths. It also dynamically changes the size of its size/flags field to accommodate growth. The minimum overhead is an extra char (same as null termination).
Re:Show HN by drfuchs on Saturday December 03, 2022 @11:18AM (#33844518)
Nit: Many Pascal compilers / runtimes extended the language in non-standard ways, including various schemes for storing string length in front of the string. But nothing like this was ever part of the ISO Pascal standard, and it was certainly not in the "PASCAL User Manual and Report" by Kathleen Jensen and Niklaus Wirth.

In fact, in standard Pascal, string handling is extremely rudimentary; there was no way to express "this variable / parameter / pointer refers to a string with a length not known at compile-time".

Re:Show HN by pjmlp on Saturday December 03, 2022 @01:06PM (#33845693)
They were on the ISO Extended Pascal, which hardly mattered because by then, USCD Pascal and Object Pascal already had taken over the world of Pascal dialects, both of which had better ways to deal with strings.

https://www.iso.org/standard/18237.html

Additionally, Modula-2 was already available in 1978, sorting out all the issues of original Pascal, with all the features needed for a safe systems programming language in the late 70's.

Re:Show HN by drfuchs on Saturday December 03, 2022 @05:41PM (#33848230)
In the late 70’s, there were production-quality Pascal compilers for DEC 20 / ITS / SAIL, Vax/VMS, IBM 360/370, together covering much of academic computing and most of the ARPAnet. Even consulting Wirth, Knuth couldn’t find suitable Modula-2 compilers available for these, so TeX used Pascal and not Modula-2. Near as we heard, it was only ever seriously used on the niche ETH workstation?
Re:Show HN by pjmlp on Sunday December 04, 2022 @01:39AM (#33851215)
That is like complaining that C wasn't available in plenty of computer hardware outside Bell Labs until early 1980's, after UNIX V6 release, was it ever seriously used outside AT&T research units?

Do you seriously expect in two years left of 70's for Modula-2 to be available everywhere?

Re:Show HN by drfuchs on Sunday December 04, 2022 @01:34PM (#33856283)
No, I was simply responding to your statement that "Modula-2 was already available in 1978". This phrasing might give the wrong impression; Modula-2's practical availability was extremely limited. No doubt it was a fine language, but it never caught on to anywhere near the extent Pascal had, before C proceeded to take over the world. If you wanted to write run-everywhere software, Modula-2 was never a tenable choice.

But we digress. The topic at hand was "How does Pascal store strings?" I stand by the pedantic statement that ISO Standard Pascal does not require string lengths to be stored with the string (nor anywhere else); there's no way for the programmer to obtain the length of a string at compile-time, never mind run-time, without resorting to some sort of compiler-specific language extension (most (all?) of which did indeed put the string length in front). Conversely, the Extended Pascal standard pretty much requires strings to instead be implemented as "fat pointers," consisting of a length and a pointer to the actual characters. I say this because substring operations return references to pieces of the original string, thus you can't stuff a length in front of the actual characters of the substring, as this would over-write characters in the original string.

Re:Show HN by tored on Saturday December 03, 2022 @02:40PM (#33846551)
Another is Hollerith strings that was used by FORTRAN and TCP protocols.

https://en.wikipedia.org/wiki/Hollerith_constant

Re:Show HN by quelsolaar on Saturday December 03, 2022 @09:20AM (#33843650)
Optimizing text is hard because you seldom up front know how much memory will be needed and allocations are slow.

Another way to do it is to use:

typedef struct{ size_t allocated; size_t used; char buffer[]; }String;

This lets the header and the string be the same allocation. Thats a huge saving. Its also useful to store the allocation and use size separatly so that you can reuse / modify buffers. The used field lets you use memcpy without looking for string termination.

You can make it even more complex by adding flags if the string is on the stack or on the heap. That way you can do things like:

String buffer = MACRO_TO_CREATE_STRING_BUFFER_ON_STACK(256), *b; b = &buffer;

b = do_processing_with_buffer(&b); // allocates on heap a larger buffer if needed

string_free(b); // frees buffer if its on heap.

Re:Show HN by jstimpfle on Saturday December 03, 2022 @10:04AM (#33843973)
In general it's hard to get more efficient than a simple struct String { const char *buffer; u32 size; }. Your method removes an indirection from the allocated storage, but you'd still need an external pointer to point to that struct in most cases. That, plus retrieving the size now costs an additional dereference. So I wouldn't use your method unless I knew that I'd have to reference the string from multiple locations.

The best way to be efficient is often to make assumptions about the data. Most strings don't need any dynamic allocation after having been "built". So it makes a ton of sense to make a string builder API that returns a final string when it's finished. In this way, you save at least the "allocated" member.

The advantage of the simpler method is that it's a representation that works for any string (or substring) that is contiguous in memory, and is completely decoupled from allocation concerns. E.g. I can easily

    #define STRING(string_literal) \
        ((String) { string_literal "", sizeof string_literal - 1 })
, to be able to statically declare such strings like this:

    String my_string = STRING("Foo bar"); 
If you have many strings that you know are small, then just the normal nul-terminated C string (without any size field) is as storage-efficient as it gets.

In practice, I find string handling so easy that I rarely even define this struct String. I just pass around strings to functions as two arguments - pointer + size. It feels so light and data flows so easily between APIs, I love it.

Re:Show HN by mh7 on Saturday December 03, 2022 @09:21AM (#33843659)
Re #3:

A big downside is that you you can't easily take ownership of an existing buffer and treat it as this string type.

string s;

char some_buf[];

string_take(&s, some_buf, some_capacity);

Also you would of course never dynamically allocate string structs, just the data member if needed.

Re:Show HN by kazinator on Saturday December 03, 2022 @09:00AM (#33843516)
Thus, sds it cannot be used for the use cases that this library allows.

This library takes string slices without having to allocate or copy memory; it seems to be for use cases involving breaking down strings in complex ways, where good ergonomics and efficiency of obtaining a null-terminated C string are secondary.

Re:Show HN by masklinn on Saturday December 03, 2022 @09:47AM (#33843842)
> The trick is the size is hidden before the adress of the buffer.("Learn this one simple trick that will change your life for ever").

This has drawbacks:

1. you can't convert an existing buffer to an sds buffer

2. you can't slice into a buffer, because the metadata is part of the string's buffer (even if it's before the pointer)

Re:Show HN by lifthrasiir on Saturday December 03, 2022 @09:53AM (#33843894)
I don't like SDS for multiple reasons. My biggest complaint is that it's a data structure disguised as a single naive pointer, which is actually harder to use correctly. This kind of "masquerading" pointer is conceptually a linear type, as you can't safely change its length in place and any potential change has to return the modified pointer somehow. No other type in C behaves like this, resulting in more confusion and thus more errors. And I have more counterpoints to those self-claimed advantages as well:

Counterpoint #1: You can't pass SDS strings to functions that accept `char **` (which is a common way to return a string of unknown length, and often can act as an in-out parameter as well).

Counterpoint #2: You rarely access individual "characters" (whatever this means). It is a conscious decision to whether you should iterate over bytes or Unicode scalar values or code points or grapheme clusters, and for this reason it is better to make the decision explicit even though it's C `char` in the surface level.

I have no evidence for nor against advantage #3 though.

Re:Show HN by kevin_thibedeau on Saturday December 03, 2022 @12:35PM (#33845287)
> No other type in C behaves like this,

Malloc does this.

Re:Show HN by lifthrasiir on Saturday December 03, 2022 @12:49PM (#33845467)
Malloc is not a type. If you meant to say realloc, a good point and it's indeed a bad interface for the exact reason but still it's not a type.
Re:Show HN by Karellen on Saturday December 03, 2022 @01:49PM (#33846137)
I believe malloc() was intended, as a number of old-school UNIX implementations of malloc() put the size of the allocation (and possibly other bookkeeping info?) "in front of" the pointer returned, in a similar way to how sds stores the size of its buffer.
Re:Show HN by schemescape on Saturday December 03, 2022 @10:05AM (#33843989)
That sounds like the same allocation scheme as used in Microsoft’s BSTR type: https://learn.microsoft.com/en-us/previous-versions/windows/...
Re:Show HN by realgeniushere on Saturday December 03, 2022 @12:24PM (#33845176)
Makes me think less of antirez that he doesn’t acknowledge that this is the same design as Microsoft’s BSTRs, which predate sds by many many years.
Re:Show HN by dixie_land on Saturday December 03, 2022 @08:27PM (#33849557)
I also thought of BSTR but in all fairness this idea of prefixing metadata for string types aren't so unique: eg Delphi's string types use a similar scheme: https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Inte...

One obvious advantage of such scheme of course is that you can pass them as-is to C APIs

Re:Show HN by jlokier on Saturday December 03, 2022 @12:29PM (#33845229)
On point 3, you can achieve the same cache locality, without losing the ability to take slices or append, by having the string object contain a pointer to the string bytes, and allocating the bytes by default immediately after the string object.

It is still single allocation, so the allocation is just as fast.

The pointer is in the same cache line as the string bytes in all strings except for slices (and any other fancy indirect string types). Even though the code fetches indirectly via that pointer, the CPU will be able to fetch the initial string byte efficiently as soon as it has the pointer.

Re:Show HN by estebank on Saturday December 03, 2022 @01:36PM (#33846004)
How would this colocation of the string pointer work? Because these would be in the heap, right? Otherwise the pointer would get invalidated as soon as the enclosing function ends and its stack frame gets discarded. So if it is in the heap then you either have a pointer to the colocated pointer (not very useful, if negligible performance impact) or you're copying the colocated pointer (at which point you're back to square one, having a pointer in the stack and the underlying string in the heap). Am I missing something?
Re:Show HN by alcover on Saturday December 03, 2022 @02:45PM (#33846601)
Good point. Some SSO (small str optimization) schemes achieve this by pointing back into the struct itself. Gcc String implementation for ex.
Re:Show HN by WalterBright on Saturday December 03, 2022 @01:44PM (#33846079)
> The trick is the size is hidden before the adress of the buffer.("Learn this one simple trick that will change your life for ever").

The length-prefix string has a major problem - it cannot be sliced to produce another length-prefix string. It has to be copied. Instead, using a phat pointer (size_t length, char* ptr) works very, very well. We've been using it in D for 20 years.

I've proposed it for C, too:

https://www.digitalmars.com/articles/C-biggest-mistake.html

Re:Show HN by quelsolaar on Saturday December 03, 2022 @02:23PM (#33846410)
The problem with arrays is not that they decay to pointers, its that they arent pointers to begin with. This:

int x[10];

Sould mean "put 10 ints in memory, and make x the pointer to it.". The thing that messes this up is sizeof. sizeof(x) doesnt give the size of the pointer like it should, it gives you the size of the array. If that was fixed (obviusly it can without breaking everything) then things would be much better and consistent.

Re:Show HN by WastingMyTime89 on Saturday December 03, 2022 @03:39PM (#33847087)
Yes, sizeof has a primitive as a weird behaviour when used in the scope of a continuous allocation. I agree it’s unfortunate.

But "arrays" definitely are pointers. I put "arrays" in quote because C has nothing I would personally call an array. It’s just contiguous memory allocation. It’s to the point that 10[a] and a[10] are desugared to the same thing.

Re:Show HN by torstenvl on Saturday December 03, 2022 @02:13PM (#33846321)
> it cannot be sliced to produce another length-prefix string

Come again? Of course it can. It can't be done in place, mind you, but that's a pretty bad way to do any string slicing, regardless of implementation, in a manual memory management environment. Do most programmers expect their slices to result in undefined behavior if they release the larger string they were made from? I doubt it.

Re:Show HN by alcover on Saturday December 03, 2022 @02:38PM (#33846544)
> Come again? Of course it can.

Oh come on.. I'm pretty sure Walter meant taking a view kind of slice. Obviously one can always copy part of a string, but that's not what slice implies I think.

> It can't be done in place, mind you, but that's a pretty bad way to do any string slicing, regardless of implementation, in a manual memory management environment.

It's not bad. It's the best, most efficient way. O(1)-ish.

> Do most programmers expect their slices to result in undefined behavior if they release the larger string they were made from? I doubt it.

That's what copy-on-write is for : release of the parent is blocked until no views are left on it.

I made a C String lib using CoW. It works well:

https://github.com/alcover/buffet

Re:Show HN by WalterBright on Saturday December 03, 2022 @06:41PM (#33848742)
The length-ptr strings are very fast and efficient:

1. don't have to make a copy

2. don't have to scan to find the length

3. don't have to load the string into the cache to find its length

As a bonus, they're memory safe.

Re:Show HN by gorgoiler on Saturday December 03, 2022 @04:20PM (#33847486)
Whoa. Jamming metadata in the address space before the string pointer is such a clever idea. I don’t know enough about C to know how many awkward bugs this might cause, but I know enough about programming to spot exceptional lateral thinking when I see it. Very neat.

I guess the SDS authors might ship a linter to spot all the times you mistakenly use free() instead of sdsfree()? That could make the cleverness more tolerable?

Re:Show HN by arcticbull on Saturday December 03, 2022 @04:31PM (#33847590)
This is a common approach for things like malloc to use, since you are passing an opaque pointer to arbitrary data into free() which you then expect to quickly do something useful with. It can just walk back the pointer a little to find the header and act on it.

It's pretty weird to see it anywhere other than malloc though especially masquerading as a basic type. It's incompatible with other common patterns like returning via (char *) and you can't identify which deallocator you're supposed to give the result to from the type alone.

Re:Show HN by Gibbon1 on Saturday December 03, 2022 @06:34PM (#33848679)
Random musing of a old firmware guy. In the past I've had issues with wanting to make sure a function isn't being passed a pointer to an object on the stack. Least in embedded land it's trivial to write a function that can tell you if an address points to something on the stack, heap, or is a global.
Re:Show HN by deterministic on Sunday December 04, 2022 @06:19PM (#33859149)
This is actually a very well known C trick.