4
\$\开始组\$
  1. C23标准化strdup()/strndup(),但被遗漏了stpcpy().stpcpy()不同于strcpy()其中,它返回一个指向复制字符串的终止空字节的指针,而不是指向目标字符串的起始地址的指针。

    它允许这样做:

    char bigString[1000];bigString[0]=“\0”;stpcy(stpcpy(stpcdy(stpcy,bigString,“John”),“Paul”,“Geoge”,“),“Joel”));

    而不是:

    char bigString[1000];bigString[0]=“\0”;//或strcpy()strcat(strcat,strcat);

    如果你不知道,strcat()使用画家Shlemiel的算法.

  2. 有两个不同版本的基本名称()(POSIX和GNU)。POSIX版本修改了路径参数,当使用静态字符串调用时可能会出现segfault,例如“/usr/”。我尝试遵循的GNU版本没有。

  3. 关于打印()血管打印(),我会直接引用手册页:

    功能打印()vasprintf()是的类似物冲刺(3)vsprintf(3),只是它们分配了一个足够大的字符串来保存输出,包括终止的空字节,并通过第一个参数返回指向它的指针。此指针应传递给免费(3)在不再需要时释放分配的存储。

    不幸的是,ISO C23也忽略了这两个优点。

  4. 这个strchrnul()不同于strchr()其中,它返回一个指向匹配字符的指针,或指向末尾的空字节的指针(即,s+字符串)如果找不到字符。我发现这很有用的一个地方是从输入字符串中删除尾随换行符。

    使用strchr(),一个人会这样做:

    char*p=strchr(s,'\n');如果(p){*p=“\0”;}

    使用strchrnul(),您可以跳过分支并直接取消引用结果:

    *strchrnul(s,'\n')='\0';
  5. 这个strcasecmp()函数对两个字符串,忽略字符的大小写。

代码:

实用程序:

#如果索引UTIL_H#定义UTIL_H 1#包括<stdarg.h>int util_vasprintf(char**restrict strp,const char fmt[restrict static 1],va_list ap);[[gnu::format(printf,2,3)]]int util_asprintf(char**restrict strp,const char fmt[restrict static 1],...); [[gnu::returns_nonnull]]char*util_stpcpy(char dest[restrict static 1],const char src[restrict static 1]);[[gnu::returns_nonnull]]常量字符*util_basename(常量字符路径[static 1]);[[gnu::pure,gnu::returns_nonnull]]字符*util_strchrnul(const字符s[static 1],int c);[[gnu::pure]]int util_strcasecmp(const char s[restrict static 1],const char t[restrict static 1]);#endif/*UTIL_H*/

实用程序c:

#包括<ctype.h>#包括<stdio.h>#包括<stdlib.h>#包括<string.h>#包括<stdarg.h>#包括“util.h”int util_vasprintf(char**restrict strp,const char fmt[restrict static 1],va_list ap){va列表ap_copy;va副本(ap_copy,ap);const int nwritten=vsnprintf(nullptr,0,fmt,ap_copy);va_end(ap_copy);if(nwriten<0){死亡;}*strp=malloc((size_t)nwritten+1);if(*strp==nullptr){死亡;}const int status=vsprintf(*strp,fmt,ap);if(状态<0){自由(*strp);死亡;}返回状态;致命:/*BSD实现在失败时将*strp设置为nullptr。Linux的叶子*内容未定义。C和POSIX都没有对此进行标准化*功能*/*strp=nullptr;返回-1;}int util_asprintf(char**restrict strp,const char fmt[static 1],…){va_list argp;va启动(argp,fmt);*strp=nullptr;int nwritten=util_vasprintf(strp,fmt,argp);va_end(argp);返回nwriten;}char*util_stpcpy(char dest[restrict static 1],const char src[restrict static 1]){const size_t len=字符串(src);return(char*)memcpy(dest,src,len+1)+len;}const char*util_basename(const char-路径[static 1]){char*const cp=strrchr(路径,'/');返回cp?cp+1:路径;}char*util_strchrnul(常量char s[static 1],int c){同时(*s){如果(*s==c){断裂;}}返回(char*)s;}int util_strcasecmp(const char s[restrict static 1],const char t[限制静态1]){整数p,q;做{p=*s++;if(islower((unsigned char)p)){p=toupper((无符号字符)p);}q=*t++;if(islower((unsigned char)q)){q=toupper((无符号字符)p);}}而(p==q&&q!='\0');返回p-q;}#ifdef测试主#包括<assert.h>int main(无效){断言(strcmp(util_basename(“/usr/lib”),“lib”,==0);断言(strcmp(util_basename(“/usr/”),“”)==0);断言(strcmp(util_basename(“usr”),“usr“)==0);断言(strcmp(util_basename(“/”),“”)==0);断言(strcmp(util_basename(“.”),“.”,==0);assert(strcmp(util_basename(“..”),“..”)==0);断言(util_strcasecmp(“aPplE”,“aPplE”)==0);断言(util_strcasecmp(“苹果”,“苹果”)==0);断言(util_strcasecmp(“HELLO”,“HELLO”)==0);断言(util_strcasecmp(“,”)==0);断言(util_strcasecmp(“HrLLO”,“HELLO”));}#结尾

我没有想到对其他函数进行有意义的测试。

审查请求:

这些实现是否正确?你看到代码中有任何缺陷或效率低下吗?

这些函数还有其他有用的属性吗?

\$\端组\$
14
  • \$\开始组\$ 如果您知道任何其他非标准有用的字符串函数,请务必提及。 \$\端组\$
    – 哈里斯
    5月17日16:04
  • \$\开始组\$ 关于的另一条注释strchrnul():Areadlines()函数可以避免N个分支strchrnul()如果它取代了strchr()但是有strcspn()也可以使用。 \$\端组\$
    – 哈里斯
    5月17日17:07
  • 1
    \$\开始组\$ @三氧化二铁while(*s&&*s!=c){++s;}返回(char*)s;我真的不应该在一行中塞得太多 \$\端组\$
    – 哈里斯
    5月18日10:19
  • 1
    \$\开始组\$ @Fe2O3第一版是for循环:for(;;++s){if(*s=='\0'||*s==c){return(char*)s;}:) \$\端组\$
    – 哈里斯
    5月18日10:26
  • 1
    \$\开始组\$ @Fe2O3哦,是的,它有,我会添加到虽然循环。 \$\端组\$
    – 哈里斯
    5月20日11:17

4个答案4

重置为默认值
5
\$\开始组\$

撞击指针

char*util_strchrnul(常量char s[static 1],int c){同时(*s){如果(*s==c){断裂;}}返回(char*)s;}

很确定你会想++增量在某个时刻。如果它最初指向非NUL并且!= c(c),然后它就永远不会移动,永远不会退出。

这是个小错误,没什么大不了的。更大的错误是忽略了代码覆盖率度量。从未执行过的源代码行很可能是错误行。我们系统地使用代码库来发现这些问题。

区域设置

util_strcasecmp()应该记录下来只处理USASCII代码点,而不是utf-8代码点。它只使用“C”语言环境,避免了广泛的字符映射。

断言(util_strcasecmp(“äpfel”,“Şpfel”)==0);断言(util_strcasecmp(“fußngerübergänge”,“Fußngerübergänge”)==0);

我看不出有什么理由岛屿()测试和关联的管道由于分支预测失误而暂停。我们可以无条件地分配破案值,当非字母字符通过身份映射时,以自己的身份回来。

这个手册页对“丢失”的映射进行了有趣的观察。

构成大写字母或小写字母的细节取决于当前的语言环境。例如,默认的“C”语言环境不知道元音变音,因此不会对它们进行转换。

在一些非英语语言环境中,有小写字母,但没有相应的大写等效字母;德语的sharpß就是一个例子。

通常我会看着{上,下}说“呸!”{六,半打},不管我们用哪种方式来破解。但最后一项似乎表明便于粉碎至更低。效果是一样的。有点奇怪岛屿()然后分配p=尖峰(p)我们不能安全断言isupper(p),然而,这就是手册页所解释的。

\$\端组\$
4
  • \$\开始组\$ “很确定你会想在某个时候++增加s。”===是的,我真傻。我在最后一刻改变了实现,在此之前我有一个棘手的循环(我喜欢它,至少它有效)。谢谢。:) \$\端组\$
    – 哈里斯
    5月17日17:05
  • \$\开始组\$ 但现在有一个大写字母ß。。。U+1E9Eẞ拉丁文大写字母尖S \$\端组\$
    – 肖恩
    5月18日0:46
  • \$\开始组\$ @肖恩。哈!可以。看起来单字节ISO拉丁语言环境的行为可能与多字节unicode不同。(我正在阅读手册页,可能需要更新。) \$\端组\$
    – J_小时
    5月18日0:52
  • \$\开始组\$ C语言环境是一个巨大的错误。这里有一个有趣的咆哮github.com/mpv-player/mpv/commit/… \$\端组\$
    – 量子阱
    5月21日2:57
\$\开始组\$

潜在的想法

C2x公司计划支持

QChar*strchr(QChar*s,int c);

这个QChar(QChar)允许一个人呼叫strchr()用一个常量字符*字符*并返回相同的类型。

我对C2x的研究还不够深入,无法很好地理解所使用的机制(可能是_通用),但也许您的代码也可以对const char*util_basename(const char-路径[static 1])?

\$\端组\$
1
  • 1
    \$\开始组\$ godbolt.org/z/nj8vWcPd是如何实现的。伦丁在我提出的StackOverflow问题上给出了这个示例。 \$\端组\$
    – 哈里斯
    5月20日11:13
\$\开始组\$

错误:比较有问题。

代码确实如此

if(islower((无符号字符)p)){p=toupper((无符号字符)p);}

然而文档

在POSIX语言环境中,strcasecmp()和strncasecmp()的行为方式应类似于字符串已转换为小写,然后执行字节比较。

要揭示重要差异,需要测试“_”,位于大小写之间ASCII码信件。

建议改为:

int util_strcasecmp(const char s[restrict static 1],const char t[限制静态1]){const unsigned char*us=(const unssigned char*)s;const unsigned char*ut=(const unssigned char*)t;无符号p,q;做{p=公差(*us++);q=公差(*ut++);}而(p==q&&q!='\0');收益率(p>q)-(p<q);}

附加说明:

  • 即使是非C2x问题,const unsigned char*us=(const unssigned char*)s;也可以正确处理烧焦签署而不是2的补语。(无符号字符)p没有。

  • (p>q)-(p<q)不会像这样溢出p-q可能在什么时候烧焦尺寸与相同整数这个常见的习惯用法被优秀的编译器处理得很好。


潜在的2倍速度strcasecmp(),请参阅strcicmp_ch()其中省略了q!='\0'循环测试。

\$\端组\$
4
  • 1
    \$\开始组\$ 在C23中,所有有符号积分类型都是二补。C23代码不需要担心实现或符号和大小表示。 \$\端组\$ 5月21日5:03
  • \$\开始组\$ 链接的答案有一个名为“所有字母都是从一个低到一个高吗?(学究式)”的部分。这不是在这里娱乐吗? \$\端组\$
    – 哈里斯
    5月21日8:09
  • \$\开始组\$ @Davislor Yes“正确处理字符有符号而不是2的补码”。不适用于C23,因为post是标记。仍然,使用const unsigned char*us=(const unssigned char*)s;允许更广泛的应用,没有缺点。 \$\端组\$ 5月22日22:44
  • \$\开始组\$ @Harith,除了简单的US-ASCII大小写映射外,不区分大小写在今天通常是UTF8字符串那个是一个很大的更大的不区分大小写的比较问题。区域设置8位问题正在从相关性中消失,这就是“所有字母都是从一个低到一个高吗?(学究式)”最适用的地方。 \$\端组\$ 5月22日22:49
2
\$\开始组\$

同时util_stpcpy()没错,它可能比针对目标体系结构进行调优的实现效率更低,因为它可以将查找结束折叠到strcpy()。我无法使GCC 14在任何优化级别上融合这些操作,并且总是以两个函数调用结束。


测试真的有意义吗岛屿()打电话之前打结器()在里面strcasecmp()? 我不知道有哪种语言环境中有一个非小写字符没有被原封不动地返回托普().我强烈怀疑打结器()¹也会更快。

cco或tollower(),更像POSIXstrcasecmp()在中使用时POSIX公司语言环境。


我很惊讶你竟然想不出任何测试打印()。脑海中浮现出不少,包括一些未指定(无效)的输入。但至少,我们应该测试一条简单的成功之路,例如

{char*formated=nullptr;int len=util_asprintf(&formated,“%d”,10);断言(len==2);断言(格式化);断言(!strcmp(格式化,“10”));自由(格式化);}
\$\端组\$

您的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.