这是我实现的 strcat 函数的代码,在 main 函数中我用 字符指针 表示字符串,程序运行出错,后把字符指针换为字符数组,程序可以正常运行。字符数组不是字符指针吗?
#include <stdio.h>
#include <stdlib.h>
char* strcat(char* dst, const char* src)
{
char * ret = dst;
while(*dst)
{
*dst++;
}
while(*src)
*dst++ = *src++;
*dst = '\0';
return ret;
}
int main()
{
// char *dst = "Hello ";
char dst[] = "Hello ";
// char *src = "World";
char src[] = "World";
printf("%s", strcat(dst, src));
return 0;
}
一道 OJ 题删除字符串的字串的字串,输入2个字符串S1和S2,删除字符串S1中出现的所有子串S2。
#include <stdio.h>
#include <string.h>
#define LEN 80
int main()
{
char s1[LEN];
char s2[LEN];
scanf("%s %s", s1, s2);
int substr_len = strlen(s2);
char *p = NULL;
while ((p = strstr(s1, s2)))
{
int l = p - s1;
s1[l] = '\0';
p += substr_len;
strcat(s1, p);
}
printf("%s", s1);
return 0;
}
我的代码通过了 OJ 却在本机上报错
[1] 13129 abort ./a.out
,
本机是 OS X,编译器是clang-600.0.56
自己也仔细阅读了代码,思路上也没发现什么问题,请问这个错误是什么原因呢?
1
jokester 2015-03-15 22:34:58 +08:00 1
朝string literal寫好像是undefined behaviour
|
2
hx1997 2015-03-15 22:36:14 +08:00 1
我是初学,说得不对还请纠正。
char *dst = "Hello "; 这样的话是把 "Hello " 这个字符串常量(!)的地址赋给了 dst,之后又传给 strcat(),而常量是不能改变的,所以接下来你懂的。 至于怎么改正,应该用 char *dst = (char *)calloc(strlen("Hello "), sizeof(char)); 分配一块空间之后再把字符串 strcpy 进去。 |
3
hx1997 2015-03-15 22:39:06 +08:00
还有,最后记得 free。
|
4
Virtao 2015-03-15 22:41:24 +08:00
第一个,数组dst分配了7byte空间,只够存储"Hello "的,后面不能再接字符了,溢出。
|
5
hx1997 2015-03-15 22:53:09 +08:00
* 上边 strlen 之后还要 + 1,忘记考虑结束符。。。
|
6
DiveIntoEyes 2015-03-15 22:56:48 +08:00 1
第一个问题strcat:
1. *dst++; // 指针++后在取内容做什么? 2. char *dst = "Hello "; 这是字符串常量不能修改,但是*dst++ = *src++;又明显修改了,报错。 改为char dst[] = "Hello ";字符串变量就OK了。 |
7
onemoo 2015-03-15 23:11:07 +08:00 1
对于第一段代码:
从实现上来说,string literal通常被放到只读数据段中,你可以用指针指向它,但是不能修改。strcat函数会修改第一个参数所指向的那段内存。 如果写为 char dst[] = "Hello ",那你是使用“hello ”来初始化dst数组,所以你可以任意操作dst。 对于第二段代码: 如果Obj-C的库函数行为与C语言一致的话。那这段代码有很多安全问题: 首先,你得验证s1和s2中的东西是否为合法的字符串,以'\0'结尾? 其次,在while循环中,strcat两个参数所指的内存不能重叠,否则这个库函数的行为不确定。 |
8
zhicheng 2015-03-15 23:30:32 +08:00 via Android 1
第一两个写法都是错的。
char *dst = "hello "; 此时 dst 指向的内存一般放在程序的 ro 段里,如果强行修改,大部分机器上内存保护都会抛异常。 char dst[] = " hello "; 此时 dst 指向的是栈上内存可以修改,并把 ro 段中的 " hello "复制过去。但是你定义时没有指定数组长度,这个时候编译器会帮你确定长度,也就是 " hello " 这个字符串的长度,包括 '\0'; 正确的是 char dst[315] = "hello "; 别的实在懒得看,你自己翻书去吧。 |
9
xieyudi1990 2015-03-16 06:38:55 +08:00 via iPhone
看到*dst++; 就没必要看了. 基本语法都有问题.
|
10
canautumn 2015-03-16 07:57:10 +08:00 1
确实,strcat中第一个循环里的*dst++不需要那个分号。dst++自增指针,然后*取值,取值以后没有用。如果用clang的话,应该会得到一个“表达式值未使用”的警告。运行倒是可以,但是这个暴露了你的理解可能有问题。应该把此处*去掉。第二个循环的*则是关键的,不能去。另外字符数组还是指针这个问题楼上说的很清楚了,一个是常量,一个是局部变量,本质不同。
第二个程序的问题就是行为未定义了。建议不熟悉的函数要查文档。文档里说的很清楚strcat的dest和src缓冲区如果有重复区域,则行为未定义。未定义就是说各个标准库的实现不同,结果可能不同,甚至出错。所以oj可能运行正确,但是你在mac上就不行。mac上的标准库strcat是用memcpy实现的,memcpy也规定了dest和src区域不能重合。memcpy的实现可能是个黑箱。当然你可能会问,用你自己在第一个例子里strcat的实现,字符一个一个的复制,即使缓冲区重合也应该不影响结果呀,确实,如果你把第一个函数改成mystrcat复制到第二个函数里,然后用你自己的mystrcat实现, 程序就会正常运行了。 c语言的字符串处理函数有无数的坑,很多函数不是你想象的那样,一定要仔细看文档。这也是为啥推荐如果能用c++处理字符的话就尽量用c++的原因。 |
11
canautumn 2015-03-16 08:07:09 +08:00 1
再补充一下,搜到一个klee自带的libc,strcat的实现和楼主的差不多(和mac的libsystem实现不一样),可以比较一下。 http://llvm.org/klaus/klee/blob/master/runtime/klee-libc/strcat.c
|
12
typcn 2015-03-16 09:40:26 +08:00 via iPhone
指针地址+1 .....
|
13
zeroday OP @jokester @hx1997 @Virtao @DiveIntoEyes @onemoo @zhicheng @xieyudi1990 @canautumn
谢谢大家的帮助,明白了。 原来字符指针指向的是字符串常量,而字符数组是将字符串常量赋值到栈上的内存中,故可以操作。第二段代码问题出在 strcat 上。 修改的代码附上。 ``` #include <stdio.h> #include <string.h> #define LEN 80 char * mystrcat(char *dst, const char *src); int main() { char s1[LEN]; char s2[LEN]; scanf("%s %s", s1, s2); int substr_len = strlen(s2); char *p = NULL; while ((p = strstr(s1, s2))) { *p = '\0'; p += substr_len; mystrcat(s1, p); } printf("%s", s1); return 0; } char * mystrcat(char *dst, const char *src) { char *ret = dst; while(*dst) { dst++; } while(*src) { *dst++ = *src++; } *dst = '\0'; return ret; } ``` |