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

初学 C 的指针和字符串,使用时出现一些问题,还请指点一下

  •  
  •   zeroday · 2015-01-21 14:23:25 +08:00 · 3127 次点击
    这是一个创建于 3391 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我的代码是要处理这样的字符串

    $GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50

    程序要读入多行这样的字符串,依据","分割字符串

    然后判断0号字段是否为"$GPRMC",2号字段是否为“A",并且验证校验和,即从字符'$'到''中间的所有字符的异或为''后面的两个字符(这两个字符为十六进制表示)

    最后将最后一个符合要求的字符串的1号字段前6个字符(即 uc t时间)转化为 bjt 时间

    在最后一步,我遇到这样一个问题,我开了一个大小为7的字符数组,将转化后的 bjt 时间用 strcpy()填充到字符数组中却得到乱码,请问是怎么回事呢?

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    char* str_sub( char str[], int start, int end );
    void str_split( char* words[], char str[], char* delim );
    int chk_sum( char sen[] );
    int hex_to_dec( char *s );
    int chk_val( char sen[] );
    void print_bjt( char uct[] );
    int is_vaild( char *words[], int chksum, int chkval );
    
    int main()
    {
        /*char str[] = "$GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50";*/
        char sen[255];
        char uct[7];
        scanf ( "%s", sen );
        while ( strcmp( sen, "END" ) != 0 )
        {
            char *words[17];
            int chksum = chk_sum( sen );
            int chkval = chk_val( sen );
            printf ( "chksum=%d chkval=%d\n", chksum, chkval );
            str_split( words, sen, "," );
    
            {
                printf ( "word[1]=%s\n", words[0] );
                printf ( "word[2]=%s\n", words[1] );
                printf ( "%s\n", sen ); 
            }
    
            if ( is_vaild( words, chksum, chkval ) == 1 )
            {
                printf ("%s\n", str_sub( words[1], 0, 6 ) );
                strncpy( uct, str_sub( words[1], 0, 6 ), 6 );
                printf ("%s\n", uct );
            }
            scanf ( "%s", sen );
        } 
        printf ("%s\n", uct );
        print_bjt( uct );
        return 0;
    }
    
    char* str_sub( char str[], int start, int end )
    {
        char new_str[end - start + 1];
        char *result =  new_str;
        for ( int i = start, j = 0; i < end - start; i++, j++ )
        {
            new_str[j] = str[i];
        }
        result[end-start] = '\0';
        return result;
    }
    
    void str_split( char* words[], char str[], char* delim )
    {
        int i = 0;
        char *buf = str;
        while ( (words[i] = strtok( buf, delim ) ) != NULL )
        {
            i++;
            buf = NULL;
        }
    }
    
    int chk_sum( char sen[] )
    {
        char *p = NULL;
        p = strchr ( sen, '$' ) + 1;
        int xor = *p++;
        while ( *p != '*' )
        {
            xor ^= *p;
            p++;
        }
        xor %= 65536;
        return xor;
    }
    
    int chk_val( char sen[] )
    {
        char *p = NULL;
        p = strchr( sen, '*' ) + 1;
        int checkval = hex_to_dec( p );
        return checkval;
    }
    
    int hex_to_dec( char *s )
    {
        int n = 0;
        int i = 0;
        for (i = 0; s[i] != '\0'; i++)
        {
            if (s[i] >= '0' && s[i] <= '9')
                n = n * 16 + s[i] - '0';
            if (s[i] >= 'a' && s[i] <= 'f')
                n = n * 16 + s[i] - 'a' + 10;
            if (s[i] >= 'A' && s[i] <= 'F')
                n = n * 16 + s[i] - 'A' + 10;
        }
        return n;
    }
    
    int is_vaild( char *words[], int chksum, int chkval )
    {
        int ret = 0;
        if ( strcmp( words[0], "$GPRMC" ) == 0 &&
                strcmp( words[2], "A" ) == 0 &&
                chksum == chkval )
        {
            ret = 1;
        }
        return ret;
    }
    
    void print_bjt( char uct[] )
    {
        int uct_h1 = ( uct[0] - '0' ) * 10;
        int uct_h2 = ( uct[1] - '0' );
        int uct_hh = uct_h1 + uct_h2;
        int bjt_hh = ( uct_hh + 8 ) % 24;
        printf ( "%2d:%c%c:%c%c", bjt_hh, uct[2], uct[3], uct[4], uct[5] );
    }
    
    23 条回复    2015-01-22 15:30:35 +08:00
    lx19891024
        1
    lx19891024  
       2015-01-21 16:37:58 +08:00
    虽然我没有看你的代码,不过下次好歹程序运行到你出错那前面打个断点,我看你已经输出%s成功了是吧(printf ("%s\n", uct );
    ),那么你肯定没搞清楚字符串转换为整数是什么意思,用atoi函数。把数据类型弄一样了再运算。
    不然你的字符'3'*10是什么意思 程序必然将'3'先转化为ascii码再做乘法,跟你想要达到效果是大相径庭的。简单的来说9+1=10. '9'+1=多少呢? 呵呵明白了吧
    fliar
        2
    fliar  
       2015-01-21 19:30:11 +08:00
    沒編過
    char new_str[end - start + 1];
    你如果要申請變長char 數組,要用new
    然後記得用完刪掉
    fliar
        3
    fliar  
       2015-01-21 19:45:37 +08:00
    你的另一個問題是strncpy的使用,你複製一個長度7的字串(0-6),但是只複製前6個沒有/0,所以你會看到亂碼
    我簡單改了一下你的代碼,這份代碼可以運行但不代表正確(比如我new 出來數組沒刪),只是說明問題在哪裡
    指針的問題你還要再研究,寫太多就長了

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>

    char* str_sub( char str[], int start, int end );
    void str_split( char* words[], char str[], char* delim );
    int chk_sum( char sen[] );
    int hex_to_dec( char *s );
    int chk_val( char sen[] );
    void print_bjt( char uct[] );
    int is_vaild( char *words[], int chksum, int chkval );

    int main()
    {
    char str[] = "$GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50";
    char sen[255];
    char uct[7];
    memcpy(sen, str, sizeof str);
    //scanf ( "%s", sen );
    while ( strcmp( sen, "END" ) != 0 )
    {
    char *words[17];
    int chksum = chk_sum( sen );
    int chkval = chk_val( sen );
    printf ( "chksum=%d chkval=%d\n", chksum, chkval );
    str_split( words, sen, "," );

    {
    printf ( "word[1]=%s\n", words[0] );
    printf ( "word[2]=%s\n", words[1] );
    printf ( "%s\n", sen );
    }

    if ( is_vaild( words, chksum, chkval ) == 1 )
    {
    printf ("%s\n", str_sub( words[1], 0, 6 ) );
    strncpy( uct, str_sub( words[1], 0, 6 ), 7 );
    printf ("%s\n", uct );
    }
    scanf ( "%s", sen );
    }
    printf ("%s\n", uct );
    print_bjt( uct );
    return 0;
    }

    char* str_sub( char str[], int start, int end )
    {
    char* new_str = new char[end - start + 1];
    char *result = new_str;
    for ( int i = start, j = 0; i < end - start; i++, j++ )
    {
    new_str[j] = str[i];
    }
    result[end-start] = '\0';
    return result;
    }

    void str_split( char* words[], char str[], char* delim )
    {
    int i = 0;
    char *buf = str;
    while ( (words[i] = strtok( buf, delim ) ) != NULL )
    {
    i++;
    buf = NULL;
    }
    }

    int chk_sum( char sen[] )
    {
    char *p = NULL;
    p = strchr ( sen, '$' ) + 1;
    int xor = *p++;
    while ( *p != '*' )
    {
    xor ^= *p;
    p++;
    }
    xor %= 65536;
    return xor;
    }

    int chk_val( char sen[] )
    {
    char *p = NULL;
    p = strchr( sen, '*' ) + 1;
    int checkval = hex_to_dec( p );
    return checkval;
    }

    int hex_to_dec( char *s )
    {
    int n = 0;
    int i = 0;
    for (i = 0; s[i] != '\0'; i++)
    {
    if (s[i] >= '0' && s[i] <= '9')
    n = n * 16 + s[i] - '0';
    if (s[i] >= 'a' && s[i] <= 'f')
    n = n * 16 + s[i] - 'a' + 10;
    if (s[i] >= 'A' && s[i] <= 'F')
    n = n * 16 + s[i] - 'A' + 10;
    }
    return n;
    }

    int is_vaild( char *words[], int chksum, int chkval )
    {
    int ret = 0;
    if ( strcmp( words[0], "$GPRMC" ) == 0 &&
    strcmp( words[2], "A" ) == 0 &&
    chksum == chkval )
    {
    ret = 1;
    }
    return ret;
    }

    void print_bjt( char uct[] )
    {
    int uct_h1 = ( uct[0] - '0' ) * 10;
    int uct_h2 = ( uct[1] - '0' );
    int uct_hh = uct_h1 + uct_h2;
    int bjt_hh = ( uct_hh + 8 ) % 24;
    printf ( "%2d:%c%c:%c%c", bjt_hh, uct[2], uct[3], uct[4], uct[5] );
    }
    zeroday
        4
    zeroday  
    OP
       2015-01-21 20:24:41 +08:00
    @lx19891024 就是输出 uct 是一个乱码,相不明白怎么会是乱码呢?
    zeroday
        5
    zeroday  
    OP
       2015-01-21 20:27:26 +08:00
    @fliar 我试着添加代码

    strncpy( uct, str_sub( words[1], 0, 6 ), 6 );
    uct[6] = '\0';

    uct 输出依旧是乱码
    davidjqq19
        6
    davidjqq19  
       2015-01-21 21:55:55 +08:00
    我这里试了下跑出来是正常结果,没乱码。

    [root@localhost test]# gcc test.c -std=c99
    [root@localhost test]# ./a.out
    $GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50
    chksum=80 chkval=80
    word[1]=$GPRMC
    word[2]=024813.640
    $GPRMC
    024813
    024813
    END
    024813
    10:48:13[root@localhost test]#
    zeroday
        7
    zeroday  
    OP
       2015-01-21 22:10:20 +08:00
    @davidjqq19 奇怪,zsh shell 和 bash shell 都试了,还是乱码

    ➜ ~ gcc test.c -std=c99
    ➜ ~ ./a.out
    $GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50
    chksum=80 chkval=80
    word[1]=$GPRMC
    word[2]=024813.640
    $GPRMC
    024813
    ?X?
    END
    ?X?
    -1:?X:?⏎
    1423
        8
    1423  
       2015-01-21 22:57:04 +08:00
    uct 用之前清空试试?
    zeroday
        9
    zeroday  
    OP
       2015-01-21 23:55:11 +08:00
    @1423
    memset(uct, 0, sizeof(uct));
    吗?
    kingcos
        10
    kingcos  
       2015-01-22 00:43:23 +08:00 via Android
    。。。为毛学完C表示看这个还是不懂。。。
    看来我连初学都不算啊。。。
    besto
        11
    besto  
       2015-01-22 00:54:19 +08:00   ❤️ 1
    @fliar 这是c99的code,需要在gcc -std=C99编译。

    LZ我给你提个醒, 这算是C的一个坑。在函数内部使用数组,出了函数,这个数组会被释放掉的。必须malloc或是使用static变量,这里用了变长,不能用static了,只能malloc了。
    贴个代码,你感受下:
    #include <stdio.h>
    char* wrong_fun(){
    char local_array[]="12312321321324343543534";
    char *will_be_delete = local_array;
    return will_be_delete;
    }
    int main(){
    printf("%s\n", wrong_fun());
    }

    再试试:static char local_array[]="12312321321324343543534";
    @zeroday
    @1423
    @davidjqq19
    NeoAtlantis
        12
    NeoAtlantis  
       2015-01-22 00:59:14 +08:00
    跑下题,我觉得这个问题可以试试用python解决= =
    canautumn
        13
    canautumn  
       2015-01-22 07:11:00 +08:00   ❤️ 1
    @zeroday @davidjqq19 一点也不奇怪。楼上说的很清楚了,你取了一个local variable的地址传回去了,函数返回后local variable都被释放了,这个地址就变成幽灵了,它指向的地址是一个薛定谔的猫,有两种状态,老值可能还存在,也可能不存在了。这个传回的指针实际上是非法的。你可能能得到正确的结果,但多半得不到正确的结果。不信你多运行几次可能显示都不一样。

    建议找一本好的C语言教材扎扎实实从头学一下。另外,能用C++就不要再用C了,C的内存管理是个大坑。用现代C++能避免很多问题。顺便说一下这种看似随机的错误,多半是非法指针搞的。
    canautumn
        14
    canautumn  
       2015-01-22 07:18:48 +08:00
    楼上还有混合C和C++的。在C99下用malloc的话,你的那行
    char new_str[end - start + 1];
    改成
    char *new_str = malloc((end - start + 1) * sizeof(char));
    就没乱码了。然后在调用这个函数之后按理说还应该free。这是最小化改动现有代码的方法,只是太不优雅了。
    zeroday
        15
    zeroday  
    OP
       2015-01-22 09:37:10 +08:00
    @kingcos 没,是我写的一点都不好。

    我简单介绍一下我的代码。

    char* str_sub( char str[], int start, int end );

    切割出传入的字符串,从下标为 start 到 end为止。
    思路是,创建一个 end-start+1 长度的字符数组,循环字符串,将字符串中下标为 start 到 end 的字符 传入到数组中,最后返回这个数组,即切割好的字符串

    void str_split( char* words[], char str[], char* delim );

    将字符串根据指定的字符切割成,字符串数组。
    这里我需要将题目中传入的句子根据逗号切割成字段,就用到它。它的实现,用到 strtok() 切割字符串,用法我是参考文档的,我做的就是将切割好的字符串,存到字符串数组中,字符串数组我定义为 char ** 不知道合不合适。

    int chk_sum( char sen[] );

    计算校验和
    根据题目要求计算 '$'到'*'的所有字符的异或值

    int hex_to_dec( char *s );
    将十六进制转化为十进制

    int chk_val( char sen[] );
    得到字符串最后一个字段'*'字符后面的两个数字

    void print_bjt( char uct[] );
    根据传入的 uct 字符串,打印 bjt ,格式为 hh:mm:ss

    int is_vaild( char *words[], int chksum, int chkval );
    根据题目要求验证,0号字段是否为"$GPRMC",2号字段是否为“A",并且验证校验和,即从字符'$'到''中间的所有字符的异或为''后面的两个字符
    davidjqq19
        16
    davidjqq19  
       2015-01-22 09:45:12 +08:00   ❤️ 1
    楼上诸位说得对,问题出在str_sub()函数,result指针指向的new_str[]数组位于栈区,函数执行完毕就被释放了,所以得到的结果不可预料,应该改为malloc动态分配。
    zeroday
        17
    zeroday  
    OP
       2015-01-22 09:45:36 +08:00
    @NeoAtlantis 一开始我用 Java 实现并且通过,只是课程要求必须用 C 实现,准备改写为 C 时发现,C 中并没有 Java的处理字符串的函数,然后自己写函数实现,但是指针字符串没过关,就遇到现在这种情况了。C 处理字符串感觉没有 Java 方便。
    zeroday
        18
    zeroday  
    OP
       2015-01-22 09:56:09 +08:00
    @besto
    @canautumn
    @davidjqq19 谢谢了,学会了 malloc 的用法。发现 C 中的指针真的很难,昨晚我在调式代码找错误时,发现如果输入的字符串不是题目的格式,比如 ”dasdas,dasdas,asdasd", 程序就提示
    terminated by signal SIGSEGV (Address boundary error)
    fliar
        19
    fliar  
       2015-01-22 12:12:50 +08:00
    @zeroday 我代碼有寫,你比下差異
    strncpy( uct, str_sub( words[1], 0, 6 ), 7 );
    fliar
        20
    fliar  
       2015-01-22 12:25:32 +08:00
    @besto c99可以這樣寫?
    char new_str[end - start + 1];
    besto
        21
    besto  
       2015-01-22 12:28:20 +08:00   ❤️ 1
    @fliar 可以。还有for (int i = 1; i < 10; i++)
    canautumn
        22
    canautumn  
       2015-01-22 13:38:52 +08:00   ❤️ 1
    @zeroday 之前没仔细看你的代码。现在发现你没必要自己写一个str_sub。你想想,strncpy不就是str_sub吗?你把
    printf ("%s\n", str_sub( words[1], 0, 6 ) );
    strncpy( uct, str_sub( words[1], 0, 6 ), 6 );
    这两行换成
    strncpy( uct, words[1], 6 );
    一样能跑通,还没有你出现的内存的问题。

    另外楼上有提到,你的uct的最后一个字符串结束符\0没有处理。最好的办法其实是在初始化uct时把uct[6]初始化为0。数组在创建时是不自动初始化的,里边的值可能是任意值,只不过程序刚开始很可能是0,但是不初始化是不安全的。应该养成好习惯。万一不是0,你的uct就可能出问题了。
    fliar
        23
    fliar  
       2015-01-22 15:30:35 +08:00
    @besto 所以可以申請變長數組喔
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2363 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 07:20 · PVG 15:20 · LAX 00:20 · JFK 03:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.