例如:
char*s = "abc";
一般的编译器会给它加个0结尾字符,最后它等于"abc\0"。如果要计算它的长度那么strlen会从头取第一个字符, 一直看到0的时候才会认为字符串结束了。所以为什么这种传统字符串会容易引起崩溃也就很简单了,如果一直找不到0怎么办呢?答案是一直找,直到操作系统发现它访问了不应该访问的地方会把它强制关闭,这就是所谓的程序崩溃了(当然了,这只其中一种处理方式,有时候C程序跑的操作系统没这么智能,甚至没有操作系统)。
所以strcpy那些传统字符串处理函数统统被现在的编码环境提示为最好不要用的已淘汰函数。在大概2000年的时候有一股热潮,将各个字符串函数替换成带最大长度限制的版本,例如strncpy (所以在阅读开源 C语言项目的时候经常会看到某个有点眼熟但是又不认识的函数,这时候一般就是这种情况)实际的开源项目中其实都有自己的字符串"类",即使是没有类语法支持的纯C项目中也是会有的,只是用C的语法罢了,实现思想就是字符串的类。这也是为什么在用C/C++框架的时候会发现VC有个CString、QT 有个QString,大家都有自己的字符串实现。假如是纯C项目自己要实现字符串也就罢了,在C++中不是有标准std::string吗?这主要是早期的std::string 有很多问题,例如vc6中的版本复制出来的字符串占用的内存是同一块,只有当修改了其中一个的内容时它才会新生成一块内存,这在多线程环境下就是灾难。
开源纯C语言项目中的自定义字符串一般是如何实现的?
一般来说,开源纯C中项目的自定义字符串为一个结构体的指针,而不是传统C语言那样的直接一个缓冲区。 以lua中的字符串为例,代码如下:
typedef struct TString {
CommonHeader;
lu_ byte extra; //reserved words for short strings; "has hash"for longs
lu_ byte shrlen; //length for short strings
unsigned int hash;
union {
size_ _t Inglen; //length for long strings
struct TString *hnext; //linked list for hash table
}u;
char contents[1];
}TString;
这里其实不用太去管其他的字段,结构体中最重要的字段是char contents[1];实际上就是char * contents;这是字符串内容实际的保存缓冲区。一般的实现中它和传统C语言字符串要保持极高的兼容性:连续的内存;有"\0"结尾。这是因为一般的字符串虽说是自定义,但会利用现有的纯C字符串处理函数去处理自己的字符串,这样实现难度就降低了。
例如lua字符串的对比函数并不会真的自己实现对比算法,而是会直接利用C的函数去对比。例如:
int luaS_ eqIngstr (TString *a, TString *b) {
size_ t len = a-> u.Inglen;
lua assert(a->tt == LUA VLNGSTR && b->tt ==LUA VLNGSTR);
return (a == b) || /* same instance 0or... */
((len == b->u.Inglen) && /* equal length and ... */
(memcmp(getstr(a), getstr(b), len)== 0)); /* equal contents*/
}
其实大多字符串实现都不会去实现对比算法,因为真正软件里用的对比算法并不是课本上看到的一个循环比对字节,例如有个算法是4个字节一起对比的。