简体   繁体   English

C99:strrpbrk 的存在(反向 strpbrk)

[英]C99: The existence of a strrpbrk (reverse strpbrk)

I am almost sure there is no reverse strpbrk() in C99.我几乎可以肯定 C99 中没有反向strpbrk() But:但是:

  1. Is there a reason for that?有什么原因吗? I mean, why does strchr() have strrchr() but strpbrk() doesn't hae strrpbrk() ?我的意思是,为什么strchr()strrchr()strpbrk()不HAE strrpbrk()

  2. How do you get the last occurrence in a string of any of the characters in another string?你如何在另一个字符串中的任何字符的字符串中最后一次出现?

  1. In my opinion, because no one thinks out of the box, stpcpy isn't part of C99 either :(在我看来,因为没有人开箱即用, stpcpy也不是 C99 的一部分:(

  2. Look at glibc's stpbrk implementation to get an inspiration, it's not that hard看glibc的stpbrk实现有灵感,没那么难

    /* Find the first occurrence in S of any character in ACCEPT. */ char * strpbrk (s, accept) const char *s; const char *accept; { while (*s != '\\0') { const char *a = accept; while (*a != '\\0') if (*a++ == *s) return (char *) s; ++s; } return NULL; }

Note that strpbrk() is NOT optimized if the second string is long and contains duplicates.请注意,如果第二个字符串很长并且包含重复项,则strpbrk()不会被优化。

One obvious thing to do would be to scan first the second string for a limited length (at most 256 bytes including the null terminator as it typically does not contain duplicates): if this string is found to be longer, it contains duplicate bytes.一件显而易见的事情是首先扫描第二个字符串的有限长度(最多 256 个字节,包括空终止符,因为它通常不包含重复项):如果发现此字符串更长,则它包含重复字节。

During this scan, it can create a bitmap (32 bytes needed if using a packed form: this can easily be allocated as an automatic array on the stack, but access to the bitmap may be longer; if you don't optimize for stack space, you may want to just create an array of 256 booleans stored as one byte for each boolean, this would use 256 bytes on the stack).在这个扫描过程中,它可以创建一个位图(如果使用打包形式需要 32 个字节:这可以很容易地作为堆栈上的自动数组分配,但对位图的访问可能会更长;如果不优化堆栈空间,您可能只想创建一个包含 256 个布尔值的数组,每个布尔值存储为一个字节,这将在堆栈上使用 256 个字节)。 That array would normally set the boolean at position 0 that will be true if the second string is shorter than 255 bytes as it would be followed by the null terminator.该数组通常会在位置 0 设置布尔值,如果第二个字符串短于 255 个字节,则该布尔值将是 true,因为它后跟空终止符。 That boolean in the array at position 0 (indicating a long second string) could be kept in a register, as well as the last position still not scanned in the second string (s2+256).数组中位置 0(表示第二个长字符串)的布尔值可以保存在寄存器中,并且最后一个位置仍未在第二个字符串(s2+256)中扫描。 Typically this initial step will be short (and its worst case is bound by design).通常这个初始步骤会很短(并且其最坏的情况受设计约束)。

Now you can scan the first string and perform simple indexing on the scanned byte to see if it is set to true in the array of boolean.现在您可以扫描第一个字符串并对扫描的字节执行简单的索引,以查看它是否在布尔数组中设置为 true。 Otherwise, check if the long string indicator is set, if not you need to scan further the second string (and continue feeding the array of booleans until you locate the character of the first string or the null terminator, update the last position scanned in that local register).否则,检查是否设置了长字符串指示符,如果没有,则需要进一步扫描第二个字符串(并继续提供布尔数组,直到找到第一个字符串的字符或空终止符,更新在该字符串中扫描的最后一个位置本地注册)。

In most cases, this will optimize a lot because you don't perform two loops (loops are costly because of their conditional jumps needing breach prediction, each string will be scanned (partly) at most once. The only price is the indirection for accessing the array of booleans in the stack, but that array is small enough to fit inside the CPU data cache (so the indirection has a virtual cost of zero).在大多数情况下,这将优化很多,因为您不执行两个循环(循环成本高,因为它们的条件跳转需要违反预测,每个字符串最多(部分)扫描一次。唯一的代价是访问的间接性堆栈中的布尔数组,但该数组足够小以适合 CPU 数据缓存(因此间接寻址的虚拟成本为零)。

This works because strpbrk() works on array of char (limited to 256 possible values).这是有效的,因为strpbrk()对 char 数组起作用(限制为 256 个可能的值)。 The initial scan of the second string will most often be complete and will set the array completely (so its null byte termination will be detected and will set the first boolean to true).第二个字符串的初始扫描通常是完整的,并将完全设置数组(因此将检测到其空字节终止并将第一个布尔值设置为真)。

A secondary optimization would be to use the value of this first boolean in the array when it is true (the second string is short) to reduce the second loop so that it will only perform indexing without retesting if it needs to scan the second string further.次要优化是在数组中第一个布尔值为真(第二个字符串很短)时使用它的值来减少第二个循环,以便它仅在需要进一步扫描第二个字符串时执行索引而不重新测试.

A profiler may reveal that you actually don't need to scan first up to 256 bytes in the first loop, and most code will typically use a second string that is at most 16 bytes long.分析器可能会发现您实际上不需要在第一个循环中首先扫描最多 256 个字节,并且大多数代码通常会使用最多 16 个字节长的第二个字符串。 You may even optimize the case where the second string is empty or contains a single character, in which case you won't need to use any array or extra register and you can either return NULL directly (the second string is empty, only a single null byte) or just scan the first string for a single character value or null byte.您甚至可以优化第二个字符串为空或包含单个字符的情况,在这种情况下,您不需要使用任何数组或额外的寄存器,您可以直接返回 NULL(第二个字符串为空,只有一个null 字节)或仅扫描第一个字符串以获取单个字符值或 null 字节。 Which length to use (for the initialization scanning the second string) could be determined on profiling your apps using that function使用哪个长度(用于初始化扫描第二个字符串)可以在使用该函数分析您的应用程序时确定

I bet that 16 would be good enough, you may find that a shorter value of 8 bytes may eventually be a bit better most of the time but at the price of using the complex branch (with the embedded loop conditionally continuing scanning the second string and updating more data in the array of booleans) more often in some cases.我敢打赌 16 就足够了,您可能会发现 8 字节的较短值在大多数情况下最终可能会好一点,但代价是使用复杂分支(嵌入式循环有条件地继续扫描第二个字符串和在某些情况下更频繁地更新布尔数组中的更多数据)。 Profiling may also help determine if you want a packed array of booleans (stored as single bits instead of full bytes) or an extended array (booleans stored as plain words: this may be architecture-dependent), or if you can use registers instead of an array (for architectures that have many registers).分析还可以帮助确定您是否需要一个打包的布尔数组(存储为单个位而不是完整字节)或扩展数组(布尔存储为纯字:这可能取决于体系结构),或者您是否可以使用寄存器而不是一个数组(对于具有许多寄存器的体系结构)。

And as I said, the only cost is stack use (but it can be limited if you pack the array; it could optionally use the heap on some architectures that have very small stacks, but using the heap has a price and generally it uses complex code that could require more heap usage and extra costs for the internal function calls and calls to system's API).正如我所说,唯一的成本是堆栈的使用(但如果你打包数组,它可能会受到限制;它可以选择在一些具有非常小的堆栈的架构上使用堆,但使用堆是有代价的,通常它使用复杂的可能需要更多堆使用和内部函数调用和系统 API 调用的额外成本的代码)。

Some extreme optimizations may also use vector instructions.一些极端优化也可能使用向量指令。 So glibc could still be largely optimized for the implementation of this function.因此,glibc 仍然可以针对此功能的实现进行很大程度的优化。


The same initialization could then be used to implement the proposed "reverse" strrpbrk() , that must scan the first string up to its terminating null byte, and keep a local pointer register storing the last position found (instead of stopping on the first occurrence found): for implementing it , you could as well call strpbrk() repeatedly in a loop until it returns NULL.然后可以使用相同的初始化来实现建议的“反向” strrpbrk() ,它必须扫描第一个字符串直到其终止空字节,并保留一个本地指针寄存器来存储找到的最后一个位置(而不是在第一次出现时停止found):为了实现它,您也可以在循环中重复调用strpbrk()直到它返回 NULL。

For that implementation, don't use strrev() for that purpose: reversing the first string first has a cost: it requires at least two loops, the first one to know its effective length, and it requires either extra storage (unbound limitation for the 1st string, so it cannot be allocated on the stack and would use the heap, which is quite costly), or it requires transforming in place before scanning it, then deallocate the extra storage or undo the in-situ transform by reversing it again: this would not work if the first string is shared by competing threads, causing possible security issues.对于该实现,不要为此目的使用strrev() :首先反转第一个字符串有一个成本:它至少需要两个循环,第一个循环才能知道其有效长度,并且它需要额外的存储(无限制第一个字符串,因此它不能在堆栈上分配并且会使用堆,这是非常昂贵的),或者它需要在扫描之前就地转换,然后释放额外的存储或通过再次反转来撤消原位转换:如果第一个字符串由竞争线程共享,这将不起作用,从而导致可能的安全问题。

Because int lastMatchIdx=strlen(haystack)-strpbrk(strrev(haystack),needles) is too easy to write?因为int lastMatchIdx=strlen(haystack)-strpbrk(strrev(haystack),needles)太容易写了? And has the same complexity (though somewhat less empirical performance)并且具有相同的复杂性(尽管经验性能稍差)

for(char* h=haystack;(h=strpbrk(h,needles))!=NULL;rightMostMatch=h++); is similarly simple同样简单

As for how to make a strrpbrk i suppose best is to repeat strrchr() for each character in second param and keep record of the highest pointer.至于如何制作 strrpbrk 我想最好是为第二个参数中的每个字符重复 strrchr() 并记录最高指针。

Regarding the strpbrk function definition i wish the Standard developers would have been more precise.关于 strpbrk 函数定义,我希望标准开发人员更精确。 There should be two variants of this function -- one that searches for one character at a time from param 2 and returns first match found (glibc variant) and one that returns the very first of the possible characters in the string (this MSVC seems to do).这个函数应该有两个变体——一个从参数 2 中一次搜索一个字符并返回找到的第一个匹配项(glibc 变体),另一个返回字符串中可能的第一个字符(这个 MSVC 似乎做)。 But i guess the world wont even be perfect...但我想这个世界甚至不会是完美的......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM