News for hackers. Stuff that matters.
Re:String tokenization in C by jstimpfle on Saturday December 15, 2018 @07:31AM (#18688141)
strtok is one of the silliest parts of the standard library. (And there are many bad ones). It's broken. It's not thread safe (yes there is strtok_r). It's needlessly hard to use. And it writes zeros to the input array. The latter means it's unfit for most use cases, including non-trivial tokenization where you want e.g. to split "a+1" into three tokens.

If you program in C please just write those four obvious lines yourself.

Re:String tokenization in C by yason on Saturday December 15, 2018 @08:16AM (#18688271)
If you program in C please just write those four obvious lines yourself.

Those are not necessarily obvious lines, there are several pitfalls to avoid, and for that reason strtok() is much longer than four lines. When it comes to the standard library functions strtok() has well-defined behaviour that is easy to reason with and near-magically approaches the string-splitting convenience close to scripting languages.

In contrast, an example of truly sickening part of stdlib is converting strings to number. The atoi()/atol() family doesn't check for errors at all so you want to use strtol(). But the way error checking works in strtol() is so complex that the man page has a specific example of how to do it correctly. All sane programmers quickly write a clean wrapper around strtol() to encode the complexity once. Now, strtok() is nothing like that.

In its simplicity, strtok() is quite versatile. A few strtok() calls can easily parse lines like:

    keyword=value1, value2, value3
that you might find in configuration files. And I mean truly in just a few lines which you might expect in Python but with C string handling? No.
Re:String tokenization in C by jstimpfle on Saturday December 15, 2018 @08:32AM (#18688314)
Here is the musl implementation.

> https://github.com/esmil/musl/blob/master/src/string/strtok....

It's a bit longer than 4 lines because strtok does things you should not want. If you insist on parsing that configuration line with strtok, go ahead and write that brittle code. It breaks as soon as you want empty strings (try "keyword=value1, , value3" with strtok) or escape sequences or other transformations, or as soon as you want to do something as basic as parsing from a stream instead of a string that is completely in memory.

So to clarify, of course you are never done with parsing in 4 lines. But even if it wasn't as braindead to overwrite the input string, the functionality strtok provides would not be worth more than 4 lines.

Re:String tokenization in C by yason on Sunday December 16, 2018 @06:45AM (#18692704)
So, here's that implementation:

    static char *p;
    if (!s && !(s = p)) return NULL;
    s += strspn(s, sep);
    if (!*s) return p = 0;
    p = s + strcspn(s, sep);
    if (*p) *p++ = 0;
    else p = 0;
    return s;
Instead of carrying that code, or something similar, with my source code or my own utility library I'd much rather have the already debugged version from the standard library.

Overwriting the input in C is more efficient than maintaining more internal state and returning a pointer and the length of each token which you would need to strncpy() to get the token into a C string. strtok() does not want to do the initial strdup() for you because only you will know whether your input can already be mutated or whether you need to use a copy.

As I pointed in the other reply, strtok() does not break on strings like "keyword=value1,, , value3" unless you skipped RTFM and expect it to do something completely different. And more often than not that's exactly what you want when parsing non-computer readable input which you can expect to take a specific form.

If you want to handle escape sequences, parse from a stream (without having the option to fgets() the next line into memory), or parse CSV tables without collapsin colunms then you will want to use something more specific to that. Luckily, strtok() was not advertised as a Swiss army knife so it's off the hook for specific parsing purposes like those.

Re:String tokenization in C by jstimpfle on Sunday December 16, 2018 @08:16AM (#18692926)
As someone else pointed out what you really want is the implementation of strspn/strcspn which is where the loop is. You don't "carry" that code along. You just write

    while (i < len && !is_token_begin(buf[i]))
        i++;
    if (i == len)
        error("End of input\n");
    start_token(tok);
    while (i < len && is_token_char(buf[i])) {
        i++;
        add_to_token(tok, buf[i]);
    }
    end_token(tok);
or something along those lines. Whatever you need. It's not rocket science. Putting highly fluctuating and project-specific code like this in a library would only have disadvantages. Not everything should be in a library. In fact, most things should not be.
Re:String tokenization in C by nly on Saturday December 15, 2018 @08:42AM (#18688343)
Unless you're using a pre-specified configuration file format (e.g. TOML), then parsing configuration files requires a general parsing library. This is a non-trivial task requiring a real parser operating over a well-specified grammar. A tokenization pipeline just won't cut it.

I worked on a project a few years ago that read its custom-format config file in line by line, chopped everything off each line following the first '#' character (to support comments), and then trimmed the whitespace. This sounds like a reasonable and elegant approach until you consider that now none of your user controlled fields (via a GUI in our case) can contain the '#' character. This effected customers, but nobody ever fixed it.

With the tools and languages out there now, there's just no excuse for this crap.

Re:String tokenization in C by yason on Sunday December 16, 2018 @06:49AM (#18692717)
Unless you're using a pre-specified configuration file format (e.g. TOML), then parsing configuration files requires a general parsing library.

If you have needs that require a general parsing library then why are you criticizing strtok()? It doesn't parse XML either, not C source code, nor any unspecified configuration file formats.

Re:String tokenization in C by nly on Monday December 17, 2018 @03:42AM (#18697682)
My point is that strtok has almost no reasonable uses in real programs.
Re:String tokenization in C by Someone on Saturday December 15, 2018 @10:35AM (#18688706)
> A few strtok() calls can easily parse lines like:

     keyword=value1, value2, value3
The challenge with parsing isn’t parsing correct inputs; it’s generating useful error messages and recovering on incorrect inputs such as

     keyword=,,value1, value2, value3,,,,
or even

     =keyword=,,value1, value2, value3,,,,
strtok isn’t the best tool for doing that.

(Yes, those could be valid inputs, but if they are, chances are they should be parsed differently)

Re:String tokenization in C by yason on Sunday December 16, 2018 @06:33AM (#18692681)
That's what I meant: strtok() is well-defined in what it does. The man page is really short. I don't understand people who complain it doesn't do something it's not supposed to do. Yet it's very useful in what it does.

Your mileage may vary but it's also a common issue having to filter out empty items out of something like ",foo,bar ,,,,baz,,xyzzy," where you only really want those four words. You will especially encounter this in parsing user input which might have extra whitespace, or badly formed lists of items.

Use strtok() to split C strings into tokens delimited by the given set of delimiters. If you want to catch each comma but skip over any whitespace and split at the first '=' only, then use something else.

Let's say if I'm going to need a configuration file for my program I'd most certainly start with something I can parse with strtok(). I would really need very specific needs to warrant a more complex format that would require a more sophisticated parser in which case using one would be a no-brainer.

Re:String tokenization in C by simias on Saturday December 15, 2018 @11:41AM (#18689065)
I use strtok_r from time to time, it does the job if you have a mutable input. Of course having to write zeroes is a bit cumbersome but it's one of the drawbacks of C-style strings.

The plain truth is that string handling in C is a huge pain in the ass no matter how you look at it. Splitting, concatenating, regex-ing... All of that is a huge pain in C. If you need to write a high-performance parser then it might be worth it but if you're just parsing a fancy command line format and performances don't matter it's just incredibly frustrating and error prone.

Rust fares better here because its str type is not NUL-terminated but actually keeps track of the length separately which makes it significantly more flexible and versatile. Of course you could do that in C but you'll be incompatible with any code dealing with native C-strings.

And of course you make one mistake and you have a buffer overflow vulnerability...

So yeah, if you program in C please use strtok_r if applicable, otherwise considering offloading the parsing to an other part of your application written in a language better suited for that and hand over binary data to the C library. If everything else fails then consider handwriting your parser and may god be with you. Oh and if your grammar is complex enough to warrant it, there's always lex/yacc.

Re:String tokenization in C by Matthias247 on Saturday December 15, 2018 @12:07PM (#18689187)
> The plain truth is that string handling in C is a huge pain in the ass no matter how you look at it.

It is. And it’s not even only Cs fault. 80% of it is bad API Design. Strings could be accepted as a struct consisting of a pointer and a length, aka string_view. And there could be some manipulation functions around it. That would make those APIs a lot more flexible (one no longer needs to care whether things are null terminated and there would be less pointless copies).

For these reasons my estimate in the meantime is that the average C program which uses stdlib functions is less efficient than an implementation in another language, even though the authors would claim otherwise (its C, it must be fast).

Re:String tokenization in C by paavoova on Saturday December 15, 2018 @12:31PM (#18689305)
What happens if you truncate your string? You lose information about buffer size. So now you need to store two sizes for such strings to be useful, string size and buffer size, which is 16 bytes for size_t on 64-bit systems. On top of that, strings are no longer arrays. So either the language would have to incorporate first class support for these strings, or you'll have people extracting the string pointer from the struct to perform indexing operations on themselves.
Re:String tokenization in C by Matthias247 on Saturday December 15, 2018 @12:43PM (#18689371)
The approach obviously only works for read access and not for mutation. However those are the most often required operations. For owned strings and mutations different APIs are required. C++ string_view vs string, and Rusts str slices vs owned Strings work like this.
Re:String tokenization in C by WalterBright on Saturday December 15, 2018 @05:39PM (#18690761)
One of the big performance wins of the D programming language over C is that arrays are length terminated instead of 0 terminated, so you can "slice" strings to get substrings, rather than allocate/copy/zero (and then get the free in the right place!).
Re:String tokenization in C by paavoova on Saturday December 15, 2018 @12:01PM (#18689157)
> but you'll be incompatible with any code dealing with native C-strings

Not entirely, see https://github.com/antirez/sds

Basically, you have a header storing length, etc, but still null terminate, so library functions like strlen are none the wiser.

Re:String tokenization in C by saagarjha on Saturday December 15, 2018 @01:09PM (#18689532)
You lose the ability to pass create zero-cost slices, though. C++11 actually implements strings like this, but iterators allow you to pass in parts of the string as necessary.
Re:String tokenization in C by the_duke on Saturday December 15, 2018 @12:10PM (#18689204)
Nim (the programming language) implements strings this way, AFAIK, with the same reason: to be easily compatible with the C ecosystem.
Re:String tokenization in C by nly on Saturday December 15, 2018 @07:44AM (#18688162)
Much of libc is terrible from an API design perspective, even given the limitations of C as a language.

libc has somehow managed to hit the sweet spot and have APIs that are both inconvenient to use properly, and perform poorly.

Re:String tokenization in C by agumonkey on Saturday December 15, 2018 @08:08AM (#18688243)
any attempt has been made to build a nicer foundational C/OS library since ?
Re:String tokenization in C by jstimpfle on Saturday December 15, 2018 @08:15AM (#18688265)
There are of course tons of code on the net, but there is no need to standardize another grab-bag of bad API calls. If you need a batteries-included library you use python or similar. If you write in C, you care so much that you largely avoid the standard library and design your code from the ground up.
Re:String tokenization in C by Matthias247 on Saturday December 15, 2018 @12:12PM (#18689212)
You should. However there are lots of organizations around that use C because that’s what’s used in their domain (E.g. embedded) AND use the standard library, because they don’t know how to do better.

The results are often worse than if they would have used a higher level language right from the start.

Re:String tokenization in C by dkrikun on Saturday December 15, 2018 @10:59AM (#18688859)
There are numerous reasons to write in C, apart from caring so much. Using strtok() is not that bad after all because its a simple function after all.
Re:String tokenization in C by nly on Saturday December 15, 2018 @08:12AM (#18688253)
BSD has some minor improvements to libc. There's also glib.

Generally though, C is just a terrible language to do anything other than write extremely low level routines in. You should probably just never use strtok() and go straight to something like Ragel or re2c for building high performance tokenizers... then call those from a higher level language.

Re:String tokenization in C by yakovdk on Saturday December 15, 2018 @12:21PM (#18689252)
Hey! I take exception to that. I've been writing shellcode all week. C is uncomfortably high level for writing low level routines!
Re:String tokenization in C by nwmcsween on Saturday December 15, 2018 @10:55AM (#18688829)
libc aka ISO, POSIX, GNU wasn't designed it was more or less evolved from what was in use.
Re:String tokenization in C by tails4e on Saturday December 15, 2018 @09:47AM (#18688553)
strncat is another good example. It's a buffer overflow waiting to happen, and worse it's the 'safe' version of strcat. The trick is the parameter you pass is not the length of the destination buffer, but the remaining length of the destination buffer. Most people really want the behaviour of strlcat.
Re:String tokenization in C by rurban on Sunday December 16, 2018 @12:47PM (#18694124)
Nope, the safe versions of strcat are strcat_s and strncat_s, with bounds check and ensured NULL termination. strlcat does no bounds check.
Re:String tokenization in C by loeg on Saturday December 15, 2018 @05:56PM (#18690812)
glibc's continued resistance to adding strlcat and strlcpy is a travesty. :-(
Re:String tokenization in C by jstimpfle on Saturday December 15, 2018 @10:47AM (#18688777)
I'd say just use memcpy.
Re:String tokenization in C by tptacek on Saturday December 15, 2018 @10:54AM (#18688820)
How does that help? You have the same bookkeeping problem with memcpy as you do with strncat.
Re:String tokenization in C by jstimpfle on Saturday December 15, 2018 @11:26AM (#18689002)
str[ln]cat is like memcpy, but it does unnecessary work (search for the end of the string which the caller should long have figured out). It's like memcpy but less general and more confusing to use due to the implicit string length of the target operand. It's a completely unnecessary API, likely a remainder from the times when people used C for scripting.

It's also a complete idiocy to possibly not copy everything. I've never wanted that. It's begging for bugs. If there's not enough room to copy all the source bytes, you either know that in advance or you want to crash hard.

Even memcpy itself is hardly necessary as a library function. It's a simple loop. But at least it's what you would write anyway, and in some cases compilers are able to optimize it (with questionable benefits).

Re:String tokenization in C by repiret on Saturday December 15, 2018 @11:44AM (#18689081)
If you use memcpy and your destination buffer is too small you don’t crash hard with any certainty, your program might even appear to work.

Moreover, most of the time I’m either certain the output buffer is large enough (maybe because I just allocated it) or I really do want the output buffer to have a truncated string. Strncpy’s two dumb behaviors that I almost never want is to pad the destination buffer with zeros if the source string is smaller and to leave the destination I terminated if the source is longer.

Re:String tokenization in C by tails4e on Saturday December 15, 2018 @01:01PM (#18689487)
Sure, however the problem I'm highlighting is most programmers are not aware this std library function is not like the other str[n] functions. There are plenty of solutions, but it was an objectively poor decision to define strncat the way it was.
Re:String tokenization in C by hermitdev on Saturday December 15, 2018 @04:35PM (#18690463)
There are valuable use cases where this matters. For example, parsing FIX messages in finance, this allows you to parse the tag/value pairs with no memory allocations, which matters in low latency HFT applications.
Re:String tokenization in C by quotemstr on Saturday December 15, 2018 @04:40PM (#18690484)
If you want speed, just build a lexer with ragel [1]. It's hard to go faster than a DFA.

[1] http://www.colm.net/open-source/ragel/

Re:String tokenization in C by Gibbon1 on Saturday December 15, 2018 @04:56PM (#18690562)
> And it writes zeros to the input array.

If you pass strtok_r a const string it can and will bus fault in some systems. This happens when it tries to write a /0 to the input string. Being an old crusty firmware guy I'm not sailing on good cargo cult ship HMS Immutability, but generating side effects in your input data stream is terrible.

There is no way to back up/undo when using strtok_r. When your parsing involves a decision tree that kinda sucks.

Re:String tokenization in C by avar on Saturday December 15, 2018 @04:55PM (#18690557)
> it writes zeros to the input array [which] means it's unfit for most use cases[...]

Other issues with strtok() aside, this seems like a silly reason to discount a standard library function. If you don't want your input munged you can strdup() it. It's rare to find a C program that's so specialized that the performance hit of a strdup() would be unacceptable in a case where strtok() could otherwise have been used.

Re:String tokenization in C by jstimpfle on Sunday December 16, 2018 @07:35AM (#18692827)
It would be wasteful to duplicate the input string which includes so much garbage. I would rather just go through the input string and append token by token to the output string, terminated with a single NUL.
Re:String tokenization in C by Hypergraphe on Sunday December 16, 2018 @04:57AM (#18692444)
Agreed, I basically avoid using strtok because of that. Why would you write zeros in my input...
Re:String tokenization in C by fredmorcos on Saturday December 15, 2018 @09:48AM (#18688558)
strtok is there for things like and similar to /etc/hosts and /etc/fstab
Re:String tokenization in C by ygra on Saturday December 15, 2018 @10:17AM (#18688643)
Or have those file formats been designed around having to parse them in a C program?
Re:String tokenization in C by fredmorcos on Saturday December 15, 2018 @11:22AM (#18688979)
Could be. I don't know which one came first.
Re:String tokenization in C by fanf2 on Saturday December 15, 2018 @09:08AM (#18688430)
Use strcspn() instead
Re:String tokenization in C by jstimpfle on Saturday December 15, 2018 @09:29AM (#18688490)

    Token tok;
    start_token(&tok);
    for (;;) {
        int c = look_next_char();
        if (('A' <= c && c <= 'Z') ||
            ('a' <= c && c <= 'z')) {  /* or whatever test */
            consume_char();
            add_to_token(tok, c);
        } else {
            break;
        }
    }
    end_token(tok);
Done. There's no point in going through a weird API.
Re:String tokenization in C by torstenvl on Saturday December 15, 2018 @11:28AM (#18689013)
You've not only hard-coded your tokenization rules inside your logic, but you've managed to make it break on anything non-ASCII. I suppose you consider isalpha() to have a weird API?
Re:String tokenization in C by jstimpfle on Saturday December 15, 2018 @11:40AM (#18689063)
I look at it the other way: I've hard coded the reading and writing routines inside the tokenization logic.

Being able to do that is exactly the point why it's so much simpler to avoid a silly API such as strcspn (or, god forbid, strtok).

> non-ASCII

yeah i know... Do you prefer strcspn("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZ")? Do you think it's faster?

If you're pedantic, you could lex (0x41 <= c && c <= 0x5A). That way at least you consistently read ASCII, even on non-ASCII implementations. But I don't care and it's less readable.

> I suppose you consider isalpha() to have a weird API?

Yes. I do not even understand what it does.

>> isalpha() checks for an alphabetic character; in the standard "C" locale, it is equivalent to (isupper(c) || islower(c)). In some locales, there may be additional characters for which isalpha() is true-letters which are neither upper case nor lower case.

Well in any case I'm sure that's not what I wanted... By the way locale is super hard to use as well. Locale is a process global property. I'm not aware of any way to pass explicit locales to library functions.

Re:String tokenization in C by avar on Saturday December 15, 2018 @04:59PM (#18690589)
> If you're pedantic, you could lex (0x41 <= c && c <= 0x5A)

'A' v.s. 0x41 makes no difference for portability. The thing that's unportable about that is that it assumes that the characters A..Z are continuous in your character encoding, which isn't portable C.

Although admittedly having to deal with EBCDIC these days is rare in anything except highly portable programs like C compilers or popular script interpreters.

This is why ctype.h functions exist. Just use them.

Re:String tokenization in C by loeg on Saturday December 15, 2018 @06:00PM (#18690821)
> 'A' v.s. 0x41 makes no difference for portability. The thing that's unportable about that is that it assumes that the characters A..Z are continuous in your character encoding, which isn't portable C.

Wait, what? If C does not require A..Z to be contiguous, the distinction between 'A' and 0x41 is extremely significant to portable programs intending to parse ASCII when the native compiler encoding is whatever franken-coding doesn't have contiguous latin characters.

Re:String tokenization in C by avar on Saturday December 15, 2018 @06:52PM (#18690989)
Yes. If the problem was trying to parse ASCII consistently that would be the right solution.

My response was to OPs moving the goal post to "portably parsing ASCII" in response to his suggested replacement for a C library function not being portable on non-ASCII systems, which make no sense.

Re:String tokenization in C by jstimpfle on Sunday December 16, 2018 @07:42AM (#18692847)
I explicitly wrote "or whatever test" as a comment to the code snippet, and obviously the test was not the point of my comment.

Anyway I think most programming languages nowadays have their source encoding specified as UTF-8 or at least something ASCII-like, so ('A' <= c && c <= 'Z') is in fact what I would likely write, and using isalpha() would technically be a bug just as well.

Re:String tokenization in C by zozbot123 on Saturday December 15, 2018 @06:03PM (#18690834)
EBCDIC famously does not have A..Z as contiguous characters, and I wouldn't describe it as a 'franken-coding' just yet - it still finds plenty of use in some places.
Re:String tokenization in C by fork1 on Saturday December 15, 2018 @06:35PM (#18690953)
Unless you're dealing with mainframes, it's not like you see it everyday.
Re:String tokenization in C by loeg on Saturday December 15, 2018 @06:40PM (#18690970)
EBCDIC is a classic example of a franken-coding.

If your compiler's source character set is EBCDIC and you want to parse ASCII files, you must use 0x41, etc, instead of 'A'.

Re:String tokenization in C by tptacek on Saturday December 15, 2018 @10:51AM (#18688801)
strcspn is ANSI C90.