C99:strrpbrk(反向strpbbrk)的存在

C99:strrpbrk(反向strpbbrk)的存在,c,c99,C,C99,我几乎可以肯定C99中没有反向strpbrk()。但是: 这有什么原因吗?我的意思是,为什么strchr()有strrchr()但是strpbrk()没有strrprbk() 如何获取另一个字符串中的任何字符在字符串中的最后一次出现 因为int lastMatchIdx=strlen(haystack)-strpbrk(strrev(haystack),pines)太容易写了?并且具有相同的复杂性(尽管经验性能稍差) for(char*h=haystack;(h=strpbrk(h,针))!=N

我几乎可以肯定C99中没有反向
strpbrk()
。但是:

  • 这有什么原因吗?我的意思是,为什么
    strchr()
    strrchr()
    但是
    strpbrk()
    没有
    strrprbk()

  • 如何获取另一个字符串中的任何字符在字符串中的最后一次出现


  • 因为
    int lastMatchIdx=strlen(haystack)-strpbrk(strrev(haystack),pines)
    太容易写了?并且具有相同的复杂性(尽管经验性能稍差)

    for(char*h=haystack;(h=strpbrk(h,针))!=NULL;rightmatch=h++)同样简单

  • 在我看来,因为没有人会开箱思考,
    stpcpy
    也不是C99的一部分:(

  • 看看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;
    }
    

  • 至于如何制作strrpbrk,我想最好的办法是 对第二个参数中的每个字符重复strrchr(),并保留最高指针的记录

    关于strpbrk函数定义,我希望标准开发人员能够更精确一些。这个函数应该有两种变体——一种是从param 2中一次搜索一个字符并返回找到的第一个匹配项(glibc变体),另一种是返回字符串中可能的第一个字符(这家MSVC似乎做到了)。 但是我想这个世界甚至不会完美…

    请注意,如果第二个字符串很长并且包含重复项,则
    strpbrk()
    不会得到优化

    一个明显的做法是首先扫描第二个字符串的有限长度(最多256个字节,包括空终止符,因为它通常不包含重复项):如果发现该字符串更长,则它包含重复的字节

    在此扫描过程中,它可以创建位图(如果使用压缩表单,则需要32字节:可以很容易地将其作为堆栈上的自动数组进行分配,但对位图的访问可能会更长;如果不优化堆栈空间,则可能只需要创建256个布尔值的数组,每个布尔值存储一个字节,这将在堆栈上使用256个字节)。该数组通常会将布尔值设置在位置0处,如果第二个字符串短于255字节,则该位置将为真,因为它后面将跟随空终止符。位置0处数组中的该布尔值(表示第二个长字符串)可以保留在寄存器中,并且第二个字符串中的最后一个位置(s2+256)仍然未扫描。通常,该初始步骤会很短(最坏的情况取决于设计)

    现在,您可以扫描第一个字符串,并对扫描的字节执行简单索引,以查看它在布尔数组中是否设置为true。否则,请检查是否设置了长字符串指示符,如果没有设置,则需要进一步扫描第二个字符串(并继续输入布尔数组,直到找到第一个字符串的字符或空终止符,更新该本地寄存器中扫描的最后一个位置)

    在大多数情况下,这将进行大量优化,因为您不执行两个循环(由于循环的条件跳转需要中断预测,因此成本很高),每个字符串都将被扫描(部分)最多一次。唯一的代价是访问堆栈中布尔数组的间接寻址,但该数组足够小,可以放入CPU数据缓存(因此间接寻址的虚拟成本为零)

    这是因为
    strpbrk()。
    第二个字符串的初始扫描通常是完整的,并将完全设置数组(因此将检测其空字节终止,并将第一个布尔值设置为true)

    第二个优化是,当第一个布尔值为true(第二个字符串很短)时,使用数组中第一个布尔值来减少第二个循环,以便在需要进一步扫描第二个字符串时,它只执行索引而不重新测试

    探查器可能会显示,您实际上不需要在第一个循环中扫描最多256个字节的第一个字符串,大多数代码通常会使用最多16个字节的第二个字符串。您甚至可以优化第二个字符串为空或包含单个字符的情况,在这种情况下,您不需要使用任何数组或额外的寄存器和u可以直接返回NULL(第二个字符串为空,只有一个NULL字节),也可以只扫描第一个字符串中的一个字符值或NULL字节。使用该函数分析应用程序时,可以确定要使用的长度(用于初始化扫描第二个字符串)

    我打赌16就足够了,您可能会发现,8字节的较短值在大多数情况下最终可能会更好一些,但代价是使用复杂分支(嵌入式循环有条件地继续扫描第二个字符串并更新布尔数组中的更多数据)在某些情况下更常见。分析还可以帮助确定您想要的是压缩布尔数组(存储为单个位而不是完整字节)还是扩展数组(布尔存储为普通字:这可能取决于体系结构),或者您是否可以使用寄存器而不是数组(对于具有多个寄存器的体系结构)

    正如我所说,唯一的成本是堆栈使用(但如果您打包阵列,它可能会受到限制;它可以选择在一些堆栈非常小的架构上使用堆,但使用堆是有代价的,通常它使用复杂的代码,这可能需要更多的堆使用以及内部函数调用和系统API调用的额外成本)

    一些极端的优化也可能使用向量指令,因此glibc仍然可以在很大程度上优化该函数的实现


    然后可以使用相同的初始化来实现建议的“reverse”
    strrpbrk()
    ,它必须扫描第一个字符串u