News for hackers. Stuff that matters.
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