[C] strtok() | strspn() | strcspn() 的用法和源码分析

本文详细介绍了C语言中的strtok、strspn和strcspn三个字符串处理函数,通过源码分析理解其工作原理和使用注意事项。strtok用于在字符串中分割出子串,但会改变原始字符串;strspn返回连续包含指定字符的子串长度;strcspn则返回连续不包含指定字符的子串长度。在使用这些函数时需要注意对字符串的处理方式,尤其是不能对静态字符串直接操作,以免引发段错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

C语言的字符串库:<string.h> 臭名昭著:它毫无作为字符串的自我修养。使用原生C语言解析文本是一件相当腌臜 的事情:在清晰的逻辑以外,还需要良好的耐心来hard code在使用python等语言时视作理所应当的低级字符串处理函数。更糟糕的是,在我这样的C初学者看来,GNU manual讲的不清不楚。这篇笔记总结通过阅读源码获得的<string.h> 函数用法。

char* strtok(char* s, const char* delim)

—— Python一个split函数搞定的事情,你怎么搞得这么复杂?
——分配内存太麻烦了,我帮你标好 \0了,你从原始字符串上自取子字符串吧。

使用

不同于strdup这样会自动分配堆内存的函数,strtok不为子字符串分配额外内存。它改动传入的字符串:把delimiter的字节改成\0。因此有这样几个注意点:

  1. 在使用这个函数之后,传入的字符串就废掉啦!传进去之前做好备份。
  2. 不可以对静态字符串使用该函数。否则SEGFAULT。
char str[] = "a=1";
char* delim = "=";
char* token = strtok(str, delim);
while (token != NULL) {
	puts(token);
	token = strtok(NULL, &delim);
}

源码分析 [link]

//strtok.c
char* strtok(char* s, const char* delim) {
	static char* olds;
	return __strtok_r(s, delim, &olds); 
}

//strtok_r.c
char *__strtok_r (char *s, const char *delim, char **save_ptr) {
	char* end;

	if (s == NULL) 
		s = *save_ptr;
	
	if (*s == '\0') {
		*save_ptr = s;
		return NULL;
	}

	/*Scan leading delimeters*/
	s += strspn(s, delim);
	if (*s == '\0') {
		*save_ptr = s;
		return NULL;
	}

	/*Find the end of the token*/
	end = s + strcspn(s, delim);
	if (*s == '\0') {
		*save_ptr = s;
		return s;
	}	
	
	/*Terminate the token and make *SAVE_PTR point past it*/
	*end = '\0';
	*save_ptr =  end+1;
	return s;
}

size_t strspn(char* s, const char* accept)

返回输入字符串中首个完全由accept中字符组成的子字符串长度。

// strspn.c

size_t STRSPN(const char *str, const char* accept) 
{
	if (accept[0] = '\0')  // super edge case hahaha
		return 0;
		
	if (__glibc_unlikely(accept[1]='\0') // 分支预测,辅助编译器生成更高效的代码
	{
		const char *a = str;
		for (; *str==*a; str++);
		return str-a;
	}
	
	/*use multiple small memsets to enable inlining on most targets ?????*/
	unsigned char table[256]; // 自建字符表格!
	unsighed char* p = memset(table, 0, 64);
	memset(p+64, 0, 64);
	memset(p+128, 0, 64);
	memset(p+192, 0, 64);

    unsigned char *s = (unsigned char*) accept;
	/* Different from strcspn it does not add the NULL on the table
	     so can avoid check if str[i] is NULL, since table['\0'] will
	     be 0 and thus stopping the loop check.  */
	do
		p[*s++] = 1;
	while (*s);

	s = (unsigned char*)str;
	if(!p[s[0]) return 0;
	if(!p[s[1]) return 1;
	if(!p[s[2]) return 2;
	if(!p[s[3]) return 3;

	s = (unsigned char*) PTR_ALIGN_DOWN(s, 4); //指针对齐
}
  • 分支预测
  • 疑惑:inlining on most targets 是啥??
  • 疑惑:这里强制指针对齐的目的?

size_t strcspn(char* s, const char* reject)

返回输入字符串中首个不含reject中字符的子字符串长度。

源码分析 [link]

size_t STRSCPN(const char* str, const char* reject)
{
	if (__glibc_unlikely(reject[0] == '\0') || __glibc_unlikely(reject[1] == '\0'))
	return __strchrnul(str, reject[0]) - str;

	/* Use multiple small memsets to enable inlining on most targets.  */
	unsigned char table[256];
	unsigned char *p = memset (table, 0, 64);
	memset (p + 64, 0, 64);
	memset (p + 128, 0, 64);
	memset (p + 192, 0, 64);
	
	unsigned char *s = (unsigned char*) reject;
	unsigned char tmp;
	do
		p[tmp = *s++] = 1;
	while (tmp);

	s = (unsigned char*) str;
	if (p[s[0]]) return 0;
	if (p[s[1]]) return 1;
	if (p[s[2]]) return 2;
	if (p[s[3]]) return 3;

	s = (unsigned char *) PTR_ALIGN_DOWN (s, 4);
	unsigned int c0, c1, c2, c3;
	do
	{
		s += 4;
		c0 = p[s[0]];
		c1 = p[s[1]];
		c2 = p[s[2]];
		c3 = p[s[3]];
	}
	while ((c0 | c1 | c2 | c3) == 0);

	size_t count = s - (unsigned char *) str;
	return (c0 | c1) != 0 ? count - c0 + 1 : count - c2 + 3;
}
  • pro: 以4为一个batch批量处理字符。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值