10 ms·
the zero terminated string is I think is computing's biggest mistake. Pascal style strings were much safer.
by mrlonglong 3mo ago
the zero terminated string is I think is computing's biggest mistake. Pascal style strings were much safer.
- fragmede 3mo agocompared to Von Newman versus Harvard architecture for LLMs? I think that's a far bigger mistake.
- pjc50 3mo agoNeumann, and .. what? In what way?
- fragmede 3mo agoPrompt injection only works because there isn't two streams of input to give to the LLM. Von Neumann being the architecture with a single shared memory for both data and instructions. If there were a clean way for the LLM model to distinguish between system messages vs user messages, we wouldn't have that problem.
- amomchilov 3mo agoI don’t know how you could keep the two isolated, without drastically dropping up the utility of LLMs. Part of their wonder is how they can behave differently depending on the data they’re working with. We like that feature when the data is the “good stuff” (docs, compiler messages, etc.), but how you tell that apart from “bad stuff” (prompt injection on official-seeming pages). We basically expose LLMs to the same social-engineering vulnerabilities that humans have.
- jackbucks 3mo agoIt was definitely an interesting way to allocate pointers. I did once have a very large project where devs didnt understand this and resolved hundreds or more off by one and memory overwrites in C due to this feature. But at the same time, I think blaming the software was kind of a cop out. Devs were in a hurry and simply didnt respect the rules. Given todays software engineer at large. Nerfing programming languages so they cant destroy things might not be a bad idea. But AI will nerf everything.
- fragmede 3mo agowhy is AI gonna nerf everything? sure it could be used as the easy button, but I just spent two hours this morning learning about the neuroscience of how memory works in the brain that I didn't mean to and now I want to run studies on how memory works. Why do you assume that AI is gonna nerf everything?
- AnimalMuppet 3mo agoAGI might. AI? No way. See, AI was trained on existing data - on all that existing C code out there (sure, and also on all the papers and articles saying what was wrong with that C code). Those bugs are in the training data, and often not marked as bugs. So when AI generates C code, is it going to avoid making the mistakes that human code made? No, it's going to generate the kind of code it was trained on. How could it be otherwise? That's not going to nerf anything.
- CamperBob2 3mo agoWhen's the last time you saw a decent coding model create a buffer-overflow bug while trying to use C strings? Serious question. Anyone else seen this happen in the last 12-18 months? If so, which model and version were you using?
- macintux 3mo agoWould you even know? Serious question. The volume of code the models can produce, the subtle ways these bugs can manifest (or even only manifest when under attack), it seems like they would be easy to overlook.
- CamperBob2 3mo agoI have a habit of getting GPT 5.5 to review everything Opus writes for me, and vice versa. The model in the reviewer role frequently finds things I overlooked myself. Occasionally in parts of the code I wrote. No modern LLM has found any buffer overflow bugs in parts of my code that originated from another LLM. Again, though, they have found one or two that were my fault.
- dietr1ch 3mo agoI think it was NULL itself. It was a long way until we realised we don't want invalid values and could use the type system to help us use special values safely.
- jkrejcha 3mo agoThe problem here is that null kinda is consequential of intentional design of the type system itself. In this way, I do think that null was discovered, rather than invented. Remember, C is a kinda "portable assembler" so the constructs in it are based relatively closely to how low level data structures are mapped out in memory. This is, and continues to be, an incredibly useful feature that makes C and C structs immensely useful concepts. Part of that does need an invalid value[1]. NULL is convenient for this and although there are some very weird JavaScript-trinity-meme-style consequences for this[2], it's such a useful concept that basically all languages that have the ability to construct pointers have a null pointer[3]. The alternative world looks like everyone inventing their own invalid values. Invalid, non-null, pointers are typically MUCH worse than null pointers for debuggability and security. If you unintentionally read/write/execute memory at 0x0 (by far the most common value for NULL), most operating systems will trap this, whereas may not necessarily if 0x12345678 is your invalid value. [1]: Stuff like IA64 had NaT bits which were effectively an extra bit for what I assume to be this sorta thing. The problem with this is that it costs an extra bit. I don't really know much about IA64, but presumably [NaT 1] + [don't care] would be your null pointers here. I think? [2]: Really what the standard, in my opinion, should have done is probably not make use of the null pointer UB for many different functions. A lot of compilers took the UB surrounding that to make incredibly dubious "optimizations" that broke stuff with zero actual performance benefit whatsoever [3]: Yes, even Rust. Although some (again in my opinion) unfortunate design decisions made it so that C-Rust FFI isn't zero cost because of how it treats spans/slices
- imtringued 3mo ago>[3]: Yes, even Rust. Although some (again in my opinion) unfortunate design decisions made it so that C-Rust FFI isn't zero cost because of how it treats spans/slices If Rust slices already make you sad, then the thing I'm cooking up will make you cry for days.
- themafia 3mo ago> Pascal style strings were much safer. The limitations were brutal. Initially you could only have 255 bytes in a string. The length of a string and the size of the allocation are now separate and you may need to think about that unused memory in your design. The problem now doubles with the introduction of UTF-8. Your string size is in bytes and you need to track characters separately. If you want to create an array of strings you either need to specify the length of all strings and accept the memory overhead or have an array of pointers to strings. If you use an array of pointers you may end up choosing to use the 'nil' value as a sentinel that means "end of list." So we're right back where we started. -- Because someone decided to downvote this HN has limited the speed at which I can reply. This site is tragic and I'm fully done with it now. You can spread propaganda and poorly sourced zeitgeist and be among friends but if you try to have a genuine conversation about programming languages you are made to be unwelcome immediately. Screw this. -- > No other data structure works like this. The linked list. > You can't mess this up in an array C happily decomposes arrays into pointers. You can erase your length information from the type. This was an intentional decision. > Strings are the only data structure that assume there will be a NULL at end. Which is why almost every string API has a version that allows you to specify the maximum length. The fact that you can use a NUL doesn't mean you have to. Which is why the concept of "sentinel values" is broadly used in many types of applications you haven't considered here.
- AlienRobot 3mo ago>The problem now doubles with the introduction of UTF-8. Your string size is in bytes and you need to track characters separately. That isn't really a problem. The problem with null-terminated strings is specifically what happens when you reach the end of the allocated array and there ISN'T a NULL character. Every string function is designed to keep going until it finds the NULL character, so if a hacker gets rid of the NULL character, he can exploit pretty much any standard string manipulation function being used elsewhere in the program to manipulate whatever memory comes AFTER the string data structure. No other data structure works like this. You can't mess this up in an array, because no function that manipulates arrays is just going to keep going until there is a null. That would be stupid because it would require users of the function to add a NULL to the end of their arrays before passing it to the function, so instead we just pass the size of the array to everything. Strings are the only data structure that assume there will be a NULL at end. By the way, I read once that if you use UTF-32 every code point will be 4 bytes, constantly, but even then a single code point isn't necessarily a single character. Text is just complicated.
- msla 3mo agoIn addition to having to pick a size for the length counter and then, later, having to differentiate between lengths in bytes, codepoints, and glyphs, you can't subdivide a Pascal string using pointer arithmetic. To pass just the end of a string into a function, you have to either copy the tail of one Pascal-style string to another with a smaller size value, or your string has to be a struct with an integer and a pointer to the actual data instead of just an integer stuck on the beginning of the string. The first is a lot of copying in some cases, the second raises the specter of structs with invalid pointers. That's not to mention the potential problems that would cause with caches.
- estebank 3mo agoThe third option is to have a variable width length: the top most bit signals whether the next byte corresponds to the length or to the start of the string.
- cornholio 3mo agoYou can have a universal variable length field, for example 2 bytes for strings < 32768, then four bytes, 8 bytes etc. On the critical short string path, it costs just a single bit test. The glyph vs byte issues need to be dealt with in both formats. The subdivision issue is a good perspective, but i would argue the performance impact of cloning substrings is dwarfed by the redundant full string reads to find length.
- lelanthran 3mo ago> You can have a universal variable length field, for example 2 bytes for strings < 32768, then four bytes, 8 bytes etc. To hold the length of a string, I'd do something similar to unicode: 7-bits for size + 1-bit for continuation, then 15 bits for size + 1 bit for continuation, then 23-bits for size + 1 bit for continuation, etc. Or maybe even do it exactly the same as unicode: 0XXX XXXX -> length of string is in those 7 bits 1XXX XXXX XXXX XXXX -> length of string is in those 7+8 bits 11XX XXXX XXXX XXXX XXXX XXXX-> length of string is in those 6+8+8 bits ... > On the critical short string path, it costs just a single bit test. A few more clock cycles compared to NULL-termination, although my alternatives above require even more clock cycles. If the hardware had instructions for sentinel values, things would be easier (Like how DOS calls used '$' termination for strings) and safer. Load a sentinel byte into a register and have dedicated copy and compare instructions that take each two addresses (src and dst) and copies (or compares) src/dst until the terminator is reached (with copy copying the sentinel as well). Considering that sentinel values are needed so often, and are so useful, it's surprising that this is not in any ISA. What we have now is kludgy workarounds in the HLL for this. It's hard to blame the HLL, because some workaround has to be implemented.
- bsder 3mo agoZero terminated string is a special case of sentinel value termination. And sentinel value terminations make a lot of sense when you have punch cards and fixed length records that you need to carve into pieces. Nobody expected any decisions they were making in the 1960s and 1970s to have any bearing on computing a half-century later. They all expected to have their mistakes long papered over by smarter people at some point. But we ALL make the mistake of underestimating inertia.
- layer8 3mo agoAlmost as bad as newline-terminated lines. ;)
- sourcegrift 3mo agoWhat's bas about them and what are the alternatives, genuinely curious since never seen them spoken of
- smackeyacky 3mo agoZero terminated strings were the basis for an awful lot of useful software. Calling them the biggest mistake in computing is a bit OTT. I haven’t programmed anything Pascal related for 30+ years but I dimly remember thinking at the time that I wished the string system wasn’t so hard to use.
- asdfasgasdgasdg 3mo agoThat useful software would not have been less useful if the strings in it were represented as size + buf.
- crackez 3mo agoOh really? Have you tried to rewrite anything to put your theory to the test? I don't think it's as straight forward as you think it is...
- smackeyacky 3mo agoExactly. The pascal I used had no way to dynamically allocate a string they were all fixed at compile time. That really sucked.
- zzrrt 3mo agoLead was the basis for an awful lot of useful gasoline. Doesn't mean it was the only solution or the best one.
- ComputerGuru 3mo agoThat argument isn’t valid. The argument would be “this string design enabled a whole lot of useful software” but that’s a different matter. (And it could very well be the case.)
- JdeBP 3mo agoA more accurate re-phrased version of the original is that they are the biggest mistake in the C language. * https://news.ycombinator.com/item?id=48614913 https://news.ycombinator.com/item?id=48614913 * https://news.ycombinator.com/item?id=24454369 https://news.ycombinator.com/item?id=24454369 * https://news.ycombinator.com/item?id=1014533 https://news.ycombinator.com/item?id=1014533
- dmazzoni 3mo ago255 characters ought to be enough for everybody, right?
- RetroTechie 3mo agoYou mean bytes.. we have multi-byte characters now.
- BobbyTables2 3mo agoPartly agree but there would have been squabbling on the data type of the size, unless it was variable length. The latter would have had other issues too. For a while, 16bit would probably have seemed too extravagant. Now 32bit would probably seem too small. For a “strongly typed” language, C is pretty damn loose where would have mattered.
- zeroonetwothree 3mo agoC is not really strongly typed.
- tialaramex 3mo ago"Strongly typed but weakly checked" It turns out that the machine is much better at the sort of boring mechanical tasks where thoroughness counts and imagination doesn't and so languages which do more, and more, and more checking pay off very well. Rust's borrowck is the obvious first thought today but say WUFFS will check that you've proved certain key properties, WUFFS doesn't need to insert runtime bounds checks for example because you've proved, before the code would compile, that you don't have any bounds misses. You might have proved it by writing bounds checks yourself of course, or likely you have an inherent mathematical rationale for why your algorithm has no misses, but either way the compiler checked your work.
- imtringued 3mo agoThis is something that has irritated me for a long time. Bounds checks and sized arrays and strings are mechanically very easy to perform by a machine. These are highly automated tasks. There are some extreme cases where they ruin performance, but in the vast majority of cases they don't matter. If you look at the type of tasks that cannot be automated, if going from no to full automation required an efficiency loss of 5%, most people would see taking the hit as an obvious choice. And this is where the problem becomes recursive. You can build a language where the runtime check becomes a compile time check. We ought to abandon the C paradigm of shifting all the work to the developer and shift more work to the machine.
- mikewarot 3mo ago[dead]
- Conscat 3mo agoClang and GCC both let you use Pascal strings in C if you would like (with `\p`). But Pascal strings aren't that useful today because the maximum length is too short.
- jxbdbd 3mo agoWhy would a pascal string be any shorter than a C string? A C string is one pointer reaching all of memory, a Pascal string is two pointers reaching all of memory
- Conscat 3mo agoA Pascal string has a leading length byte. Because that is one byte, the text can't exceed 255 characters.
- t-3 3mo agoFor modern hardware, a 64-bit length is more practical though - no alignment issues. It seems to me that Pascal's specifying a single byte prefix was a language design "mistake" of the same type as NULL termination, putting hardware considerations into the language definition. Very practical for machines of the time, but not necessarily the best choice in hindsight.
- badsectoracula 3mo agoThe original Pascal didn't had a string type, that was introduced by various dialects. FWIW all Pascal dialects since the 90s have a string type that allows more than 255 bytes. In Free Pascal strings are pointers to the first character with a header in a negative offset indicating the length, reference count and codepage (these fields are aligned depending on the CPU). For C compatibility the string is also null terminated so you can pass such a string to a C function and it'll work as expected. AFAIK Delphi also does the same.
- robocat 3mo ago> single byte prefix was a language design "mistake" Easy to say in hindsight. It was an optimisation made back when every single byte mattered because you might have some kilobytes of memory and a 6502 CPU (where you strongly avoided using 16 bit pointers or arithmetic - because your program would be too bloated otherwise). At the time Pascal was used, a whole byte for each string was seen as a waste - so fixed length strings were often used instead.
- lelanthran 3mo ago> the zero terminated string is I think is computing's biggest mistake. No. They had trade-offs to make, and sentinel-based sequences are a needed thing, even outside of strings. The mistake was that ISAs never looked at what HLL needed, then add the necessary instructions (I posted more about this below). Even NULL is not a big mistake, when looked at in context of the time in which it was developed.
- layer8 3mo agoThere is a middle ground that Visual Basic (and then COM) took, with the BSTR type: It’s still a pointer to a zero-terminated char array, but there is a length field immediately preceding the first pointed-to byte. This is still compatible with a C string (assuming no embedded null characters), but BSTR-typed functions can take advantage of the length value.
- tremon 3mo ago> This is still compatible with a C string Strictly speaking, it's not alignment compatible from CString to BSTR unless you declare all strings to be at most 255 characters or the cpu architecture doesn't require aligned access for multi-byte words (like x86). The BSTR alignment must match the alignment of the length word, meaning you can't convert a randomly-aligned C string to BSTR by simply attaching a prefix in-place. Also, having the length embedded in the value rather than in the pointer makes it impossible to create BSTR (sub)slices without performing a memcpy. Fat pointers do not have this restriction.
- layer8 3mo agoA BSTR object is compatible with functions expecting a C string. The other direction obviously never holds, unless the C string is a BSTR to start with. Yes, there is a trade-off between slices using the same format and having compatibility with C strings. Hence “middle ground”. You can still use a string-slice type on top of BSTR, it just would be a separate additional type. Note that languages like Java also don’t have a singular type for strings and string slices.
- jiggawatts 3mo agoThese are great for "data smuggling" attacks where one layer of code assumes the length is 'x' and another layer assumes it is 'y'. It makes hybrids like this very dangerous for anything even remotely security-adjacent, such as roles, tokens, etc. This kind of thing caused the CVE-2009-2408 and CVE-2009-2510 "Null Truncation in X.509 Common Name Vulnerability."
- badsectoracula 3mo ago
- sourcegrift 3mo agoRust has "pascal style strings" (quotes because the concept is slightly different) so it's not a done deal