Substracting char* in C

Viewed 85

I am reading through this blog post: https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/

I am confused on how:

size_t len = end - str;

If I am correct, strings in C are represented as arrays and C strings have pointers to the first element of that array. So is that above line playing with array subscripts?

They posts these lines:

size_t strlen_cacher(char *str) {
    static char *start;
    static char *end;
    size_t len;
    const size_t cap = 20000;

    // if we have a "cached" string and current pointer is within it
    if (start && str >= start && str <= end) {
        // calculate the new strlen
        len = end - str;

        // if we're near the end, unload self
        // we don't want to mess something else up
        if (len < cap / 2)
            MH_DisableHook((LPVOID)strlen_addr);

        // super-fast return!
        return len;
    }

    // count the actual length
    // we need at least one measurement of the large JSON
    // or normal strlen for other strings
    len = builtin_strlen(str);

    // if it was the really long string
    // save it's start and end addresses
    if (len > cap) {
        start = str;
        end = str + len;
    }

    // slow, boring return
    return len;
}
3 Answers

if (start && str >= start && str <= end) { is undefined behavior (UB) unless start and end are within the same object that str points to.

strlen_cacher("Hello");
strlen_cacher("World"); // UB

In C it is UB to compare with >, >=, <=, < unrelated pointers.


"He posts these lines:" --> Take care when importing other's code.

Let's assume the code is correct and end is a pointer to the string nul terminator, i.e. the last element of the char array.

This is simple pointer arithmetic. In your example, since you pass the string as an argument, it will decay to a pointer to its first element, subtracting the pointer to the first element of an array from a pointer to any other element of the same array will give you the offset in number of elements between those two pointers, this is true for char or for any other type.

As exemplified in the following code:

#include <stdio.h>

int main() {

    char str[] = "hello";

    char *end = str + 5; // will point to the last element of str, the nul byte
                         // in the above expression str will decay 

    printf("%td", end - str); // 5
    // when you pass str to printf, again it decays
}

This code attempts to accelerate the strlen function called repeatedly on large strings by cacheing the last large string boundaries and returning the distance from str to end if str is between start and end. The distance end - str is the length of the string if end points to the null terminator.

This is risky because:

  • the test str >= start && str <= end actually has undefined behavior if all pointers do not point to the same array. This test would fail spectacularly on ancient 16-bit segmented architectures where only the offset part was compared for <, <=, > and >=.

  • this approach assumes that the long string does not change between calls to strlen_cacher. This may be true for the game loader in the referenced page, but not true in general. For example calling strlen on a long line read by getline() and then on each token carved from it by strtok will return incorrect results. The patch disables itself after the load phase to avoid this pitfall.

Related