关于指针与字符串数组的疑问

2015-03-15 22:07:31 +08:00
 zeroday

这是我实现的 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

自己也仔细阅读了代码,思路上也没发现什么问题,请问这个错误是什么原因呢?

1340 次点击
所在节点    C
14 条回复
jokester
2015-03-15 22:34:58 +08:00
朝string literal寫好像是undefined behaviour
hx1997
2015-03-15 22:36:14 +08:00
我是初学,说得不对还请纠正。

char *dst = "Hello "; 这样的话是把 "Hello " 这个字符串常量(!)的地址赋给了 dst,之后又传给 strcat(),而常量是不能改变的,所以接下来你懂的。

至于怎么改正,应该用 char *dst = (char *)calloc(strlen("Hello "), sizeof(char)); 分配一块空间之后再把字符串 strcpy 进去。
hx1997
2015-03-15 22:39:06 +08:00
还有,最后记得 free。
Virtao
2015-03-15 22:41:24 +08:00
第一个,数组dst分配了7byte空间,只够存储"Hello "的,后面不能再接字符了,溢出。
hx1997
2015-03-15 22:53:09 +08:00
* 上边 strlen 之后还要 + 1,忘记考虑结束符。。。
DiveIntoEyes
2015-03-15 22:56:48 +08:00
第一个问题strcat:
1. *dst++; // 指针++后在取内容做什么?
2. char *dst = "Hello "; 这是字符串常量不能修改,但是*dst++ = *src++;又明显修改了,报错。
改为char dst[] = "Hello ";字符串变量就OK了。
onemoo
2015-03-15 23:11:07 +08:00
对于第一段代码:
从实现上来说,string literal通常被放到只读数据段中,你可以用指针指向它,但是不能修改。strcat函数会修改第一个参数所指向的那段内存。
如果写为 char dst[] = "Hello ",那你是使用“hello ”来初始化dst数组,所以你可以任意操作dst。

对于第二段代码:
如果Obj-C的库函数行为与C语言一致的话。那这段代码有很多安全问题:
首先,你得验证s1和s2中的东西是否为合法的字符串,以'\0'结尾?
其次,在while循环中,strcat两个参数所指的内存不能重叠,否则这个库函数的行为不确定。
zhicheng
2015-03-15 23:30:32 +08:00
第一两个写法都是错的。
char *dst = "hello ";
此时 dst 指向的内存一般放在程序的 ro 段里,如果强行修改,大部分机器上内存保护都会抛异常。

char dst[] = " hello ";
此时 dst 指向的是栈上内存可以修改,并把 ro 段中的 " hello "复制过去。但是你定义时没有指定数组长度,这个时候编译器会帮你确定长度,也就是 " hello " 这个字符串的长度,包括 '\0';

正确的是
char dst[315] = "hello ";

别的实在懒得看,你自己翻书去吧。
xieyudi1990
2015-03-16 06:38:55 +08:00
看到*dst++; 就没必要看了. 基本语法都有问题.
canautumn
2015-03-16 07:57:10 +08:00
确实,strcat中第一个循环里的*dst++不需要那个分号。dst++自增指针,然后*取值,取值以后没有用。如果用clang的话,应该会得到一个“表达式值未使用”的警告。运行倒是可以,但是这个暴露了你的理解可能有问题。应该把此处*去掉。第二个循环的*则是关键的,不能去。另外字符数组还是指针这个问题楼上说的很清楚了,一个是常量,一个是局部变量,本质不同。

第二个程序的问题就是行为未定义了。建议不熟悉的函数要查文档。文档里说的很清楚strcat的dest和src缓冲区如果有重复区域,则行为未定义。未定义就是说各个标准库的实现不同,结果可能不同,甚至出错。所以oj可能运行正确,但是你在mac上就不行。mac上的标准库strcat是用memcpy实现的,memcpy也规定了dest和src区域不能重合。memcpy的实现可能是个黑箱。当然你可能会问,用你自己在第一个例子里strcat的实现,字符一个一个的复制,即使缓冲区重合也应该不影响结果呀,确实,如果你把第一个函数改成mystrcat复制到第二个函数里,然后用你自己的mystrcat实现, 程序就会正常运行了。

c语言的字符串处理函数有无数的坑,很多函数不是你想象的那样,一定要仔细看文档。这也是为啥推荐如果能用c++处理字符的话就尽量用c++的原因。
canautumn
2015-03-16 08:07:09 +08:00
再补充一下,搜到一个klee自带的libc,strcat的实现和楼主的差不多(和mac的libsystem实现不一样),可以比较一下。 http://llvm.org/klaus/klee/blob/master/runtime/klee-libc/strcat.c
typcn
2015-03-16 09:40:26 +08:00
指针地址+1 .....
zeroday
2015-03-16 11:02:30 +08:00
@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;
}
```
onemoo
2015-03-17 00:25:08 +08:00
@zeroday
在我上一次回复时,只是提到dst作为数组就可以“任意操作”了,但你要注意:如果通过指针来操作数组,千万要小心不要访问越界!
相比来说,10L @canautumn 的回答要更全面,请参考之。

关于“字符数组不是字符指针吗?”
数组和指针是不同的类型,只不过它们之间可以进行自动转换:
数组在用作右值时会自动转换为指向其首元素的指针。(除了&取地址、sizeof等情况)
通常,函数的参数被声明为数组时,会被视为指针。 注:另有static和其他限定符可以一定程度上影响传参等行为

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/177114

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX