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

2015-01-21 14:23:25 +08:00
 zeroday

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

$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] );
}
3223 次点击
所在节点    问与答
23 条回复
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
2015-01-21 19:30:11 +08:00
沒編過
char new_str[end - start + 1];
你如果要申請變長char 數組,要用new
然後記得用完刪掉
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
2015-01-21 20:24:41 +08:00
@lx19891024 就是输出 uct 是一个乱码,相不明白怎么会是乱码呢?
zeroday
2015-01-21 20:27:26 +08:00
@fliar 我试着添加代码

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

uct 输出依旧是乱码
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
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
2015-01-21 22:57:04 +08:00
uct 用之前清空试试?
zeroday
2015-01-21 23:55:11 +08:00
@1423
memset(uct, 0, sizeof(uct));
吗?
kingcos
2015-01-22 00:43:23 +08:00
。。。为毛学完C表示看这个还是不懂。。。
看来我连初学都不算啊。。。
besto
2015-01-22 00:54:19 +08:00
@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
2015-01-22 00:59:14 +08:00
跑下题,我觉得这个问题可以试试用python解决= =
canautumn
2015-01-22 07:11:00 +08:00
@zeroday @davidjqq19 一点也不奇怪。楼上说的很清楚了,你取了一个local variable的地址传回去了,函数返回后local variable都被释放了,这个地址就变成幽灵了,它指向的地址是一个薛定谔的猫,有两种状态,老值可能还存在,也可能不存在了。这个传回的指针实际上是非法的。你可能能得到正确的结果,但多半得不到正确的结果。不信你多运行几次可能显示都不一样。

建议找一本好的C语言教材扎扎实实从头学一下。另外,能用C++就不要再用C了,C的内存管理是个大坑。用现代C++能避免很多问题。顺便说一下这种看似随机的错误,多半是非法指针搞的。
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
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
2015-01-22 09:45:12 +08:00
楼上诸位说得对,问题出在str_sub()函数,result指针指向的new_str[]数组位于栈区,函数执行完毕就被释放了,所以得到的结果不可预料,应该改为malloc动态分配。
zeroday
2015-01-22 09:45:36 +08:00
@NeoAtlantis 一开始我用 Java 实现并且通过,只是课程要求必须用 C 实现,准备改写为 C 时发现,C 中并没有 Java的处理字符串的函数,然后自己写函数实现,但是指针字符串没过关,就遇到现在这种情况了。C 处理字符串感觉没有 Java 方便。
zeroday
2015-01-22 09:56:09 +08:00
@besto
@canautumn
@davidjqq19 谢谢了,学会了 malloc 的用法。发现 C 中的指针真的很难,昨晚我在调式代码找错误时,发现如果输入的字符串不是题目的格式,比如 ”dasdas,dasdas,asdasd", 程序就提示
terminated by signal SIGSEGV (Address boundary error)
fliar
2015-01-22 12:12:50 +08:00
@zeroday 我代碼有寫,你比下差異
strncpy( uct, str_sub( words[1], 0, 6 ), 7 );
fliar
2015-01-22 12:25:32 +08:00
@besto c99可以這樣寫?
char new_str[end - start + 1];

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

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

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

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

© 2021 V2EX