19

Inspired by this question about the following code from SQLite3:

 static int strlen30(const char *z){
    const char *z2 = z;
    while( *z2 ){ z2++; }
    return 0x3fffffff & (int)(z2 - z);
 }

that is accompanied by a commit message saying this function helps with int overflows.

I'm particularly interested in this part:

 const char *z2 = z;
 while( *z2 ){ z2++; }

to me this loop advances z2 until z2 points onto null terminator. Then z2-z yields the string length.

Why not use strlen() for this part and rewrite like this:

return 0x3fffffff & (int)(strlen(z));

Why use loop+subtraction instead of strlen()? What can loop+subtraction do what strlen() can't?

Community
  • 1
  • 1
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • I saw the other question. Looks like a case of NIH syndrome (http://en.wikipedia.org/wiki/Not_Invented_Here), but I hope there is a better reason. – Rudy Velthuis Jul 27 '11 at 10:33
  • OP refers to that question above. – Rudy Velthuis Jul 27 '11 at 10:35
  • 6
    I really don't get why this is close-voted. I don's ask what the intent of `strlen30()` is - that function does something extra, I only ask about why reimplement `strlen()`. – sharptooth Jul 27 '11 at 10:41
  • possibly because strlen, the way it is implemented, is susceptable to overflow if the null terminator does not exist in the string. So they rewrote it to work around this vulnerability. I don't know what the 0x3fffffff part is trying do; however. –  Jul 27 '11 at 10:46
  • 3
    @0A0D: To me the loop will fail on non-terminated strings just as hard as `strlen()`. – sharptooth Jul 27 '11 at 10:48
  • 1
    This code on its own still has overflow-related issues. `ptrdiff_t` could be bigger than `int`, in which case the value can exceed the bounds of `int`. Not technically an "overflow", since that term strictly means exceeding the bounds of an `int` during `int` arithmetic and is UB, but the result of the conversion is implementation-defined and so it isn't necessarily much use. So either the code is still flawed, or else there are some extra assumptions about implementation behavior stated somewhere in the vast sqlite code or documentation... – Steve Jessop Jul 27 '11 at 10:54
  • @Steve Jessop: Let's forget about casting to `int` for the sake of this question. I'm particularly interested in the loop. – sharptooth Jul 27 '11 at 10:56
  • 2
    Possibly what the comment means, though, is not that this function never overflows, but that what the caller does with the result never overflows. Clipping to 30 bits has the property that you can do `strlen30(a) + strlen30(b)`, and the result fits in a 32 bit signed `int` without overflowing. That result may be completely meaningless because a value was truncated inside `strlen30`, but that addition (e.g. a string concatenation) doesn't overflow. – Steve Jessop Jul 27 '11 at 10:56
  • @Steve Jessop: Okay, but this doesn't help with the loop anyway. – sharptooth Jul 27 '11 at 10:57
  • @sharptooth: sure, I see what the question is about but I agree with you that it's wrong. The loop is no safer than `strlen`. I'm also pointing out that the comment out of context seems dodgy too. Whether the comment is wrong (hence the code suspect), or there's context I'm missing, I don't think I can get far analysing the commenter's decisions. Hence I'm commenting, not answering, to the effect of "this makes no sense anyway!" :-) – Steve Jessop Jul 27 '11 at 10:58
  • I agree that strlen would work as well as the custom loop. However, this seems like a silly way to deal with overly long strings. If the string is too long, this function will hide that, by pretending it is shorter. An error message would seem a more appopriate way to handle this. –  Jul 27 '11 at 10:58
  • FWIW, I do not think this is an exact duplicate. The title is rather similar, indeed, but the question is a totally different one. – Rudy Velthuis Jul 27 '11 at 11:27
  • SQLite has additional restrictions due to the way it expresses values on disk; those high bits are used for type signaling IIRC. – Donal Fellows Jul 28 '11 at 08:30
  • @Donal Fellows: I guess this comment belongs to the linked question - it doesn't explain the loop. – sharptooth Jul 28 '11 at 08:33
  • @sharptooth: I wasn't commenting on the loop, which looks pretty much exactly like how you'd specify strlen. (For “why not use strlen itself?” I'd just ask the author of the code rather than speculating; Richard Hipp is really very approachable…) – Donal Fellows Jul 28 '11 at 10:56

2 Answers2

7

I can't tell you the reason why they had to re-implement it, and why they chose int instead if size_t as the return type. But about the function:

/*
 ** Compute a string length that is limited to what can be stored in
 ** lower 30 bits of a 32-bit signed integer.
 */
static int strlen30(const char *z){
    const char *z2 = z;
    while( *z2 ){ z2++; }
    return 0x3fffffff & (int)(z2 - z);
}



Standard References on Truncation, Types, Overflow

The standard says in (ISO/IEC 14882:2003(E)) 3.9.1 Fundamental Types, 4.:

Unsigned integers, declared unsigned, shall obey the laws of arithmetic modulo 2n where n is the number of bits in the value representation of that particular size of integer. 41)

...

41): This implies that unsigned arithmetic does not overflow because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting unsigned integer type

That part of the standard does not define overflow-behaviour for signed integers. If we look at 5. Expressions, 5.:

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression is a constant expression (5.19), in which case the program is ill-formed. [Note: most existing implementations of C + + ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all floating point exceptions vary among machines, and is usually adjustable by a library function. ]

So far for overflow.

As for subtracting two pointers to array elements, 5.7 Additive operators, 6.:

When two pointers to elements of the same array object are subtracted, the result is the difference of the subscripts of the two array elements. The type of the result is an implementation-defined signed integral type; this type shall be the same type that is defined as ptrdiff_t in the header (18.1). [...]

Looking at 18.1:

The contents are the same as the Standard C library header stddef.h

So let's look at the C standard (I only have a copy of C99, though), 7.17 Common Definitions :

  1. The types used for size_t and ptrdiff_t should not have an integer conversion rank greater than that of signed long int unless the implementation supports objects large enough to make this necessary.

No further guarantee made about ptrdiff_t. Then, Annex E (still in ISO/IEC 9899:TC2) gives the minimum magnitude for signed long int, but not a maximum:

#define LONG_MAX +2147483647

Now what are the maxima for int, the return type for sqlite - strlen30()? Let's skip the C++ quotation that forwards us to the C-standard once again, and we'll see in C99, Annex E, the minimum maximum for int:

#define INT_MAX +32767



Summary about the truncation part

  1. Usually, ptrdiff_t is not bigger than signed long, which is not smaller than 32bits.
  2. int is just defined to be at least 16bits long.
  3. Therefore, subtracting two pointers may give a result that does not fit into the int of your platform.
  4. We remember from above that for signed types, a result that does not fit yields undefined behaviour.
  5. strlen30 does applies a bitwise or upon the pointer-subtract-result:

          | 32 bit                         |
ptr_diff  |10111101111110011110111110011111| // could be even larger
&         |00111111111111111111111111111111| // == 3FFFFFFF<sub>16</sub>
          ----------------------------------
=         |00111101111110011110111110011111| // truncated

That prevents undefiend behaviour by truncation of the pointer-subtraction result to a maximum value of 3FFFFFFF16 = 107374182310.

I am not sure about why they chose exactly that value, because on most machines, only the most significant bit tells the signedness. It could have made sense versus the standard to choose the minimum INT_MAX, but 1073741823 is indeed slightly strange without knowing more details (though it of course perfectly does what the comment above their function says: truncate to 30bits and prevent overflow).



"Why not use strlen() for this part"

and rewrite it like this:

return 0x3fffffff & (int)(strlen(z));

My guess is that they wanted to avoid a potential indirection. Another advantage might be fewer dependencies on the standard library, which can be useful if you write a non-hosted application.

Btw, as follows from the references above, (int)(strlen(z)) might yield undefined behaviour if the maximum for ptrdiff_t > INT_MAX, so (int)(0x3fffffff & strlen(z)) would be better.

Sebastian Mach
  • 38,570
  • 8
  • 95
  • 130
1

Why reimplement strlen as loop+subtraction?

I suspect the real answer is that the programmer felt like it, but another potential justification/rationalisation is that the loop is inline (independent of whether strlen30 itself is), whereas on many systems strlen is an out-of-line function call (e.g. Linux/GCC). If the overwhelming majority of strings are empty or short (despite the "special" treatment of long ones), then that may yield a slight performance bump for the common case. That possibility alone may be enough to get a code-happy programmer key-tapping. For longer strings I would expect the library strlen to be generally optimal (allowing for it's lack of knowledge of the application specific length of strings).

Some systems may not even benefit from this inlining as strlen provides it's own, or an inline/out-of-line hybrid with a quick inline check for empty, one-char, maybe two-char strings then a call.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252