V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zeroday
V2EX  ›  C

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

  •  
  •   zeroday · 2015-03-15 22:07:31 +08:00 · 1340 次点击
    这是一个创建于 3541 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    14 条回复    2015-03-17 00:25:08 +08:00
    jokester
        1
    jokester  
       2015-03-15 22:34:58 +08:00   ❤️ 1
    朝string literal寫好像是undefined behaviour
    hx1997
        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 进去。
    hx1997
        3
    hx1997  
       2015-03-15 22:39:06 +08:00
    还有,最后记得 free。
    Virtao
        4
    Virtao  
       2015-03-15 22:41:24 +08:00
    第一个,数组dst分配了7byte空间,只够存储"Hello "的,后面不能再接字符了,溢出。
    hx1997
        5
    hx1997  
       2015-03-15 22:53:09 +08:00
    * 上边 strlen 之后还要 + 1,忘记考虑结束符。。。
    DiveIntoEyes
        6
    DiveIntoEyes  
       2015-03-15 22:56:48 +08:00   ❤️ 1
    第一个问题strcat:
    1. *dst++; // 指针++后在取内容做什么?
    2. char *dst = "Hello "; 这是字符串常量不能修改,但是*dst++ = *src++;又明显修改了,报错。
    改为char dst[] = "Hello ";字符串变量就OK了。
    onemoo
        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两个参数所指的内存不能重叠,否则这个库函数的行为不确定。
    zhicheng
        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 ";

    别的实在懒得看,你自己翻书去吧。
    xieyudi1990
        9
    xieyudi1990  
       2015-03-16 06:38:55 +08:00 via iPhone
    看到*dst++; 就没必要看了. 基本语法都有问题.
    canautumn
        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++的原因。
    canautumn
        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
    typcn
        12
    typcn  
       2015-03-16 09:40:26 +08:00 via iPhone
    指针地址+1 .....
    zeroday
        13
    zeroday  
    OP
       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
        14
    onemoo  
       2015-03-17 00:25:08 +08:00   ❤️ 1
    @zeroday
    在我上一次回复时,只是提到dst作为数组就可以“任意操作”了,但你要注意:如果通过指针来操作数组,千万要小心不要访问越界!
    相比来说,10L @canautumn 的回答要更全面,请参考之。

    关于“字符数组不是字符指针吗?”
    数组和指针是不同的类型,只不过它们之间可以进行自动转换:
    数组在用作右值时会自动转换为指向其首元素的指针。(除了&取地址、sizeof等情况)
    通常,函数的参数被声明为数组时,会被视为指针。 注:另有static和其他限定符可以一定程度上影响传参等行为
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2728 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 06:36 · PVG 14:36 · LAX 22:36 · JFK 01:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.