字符串常见操作错误

  1. 无界字符串复制
  2. 差一错误
  3. 空结尾错误
  4. 字符串截断

1. 无界字符串错误

不管是C还是C++,在从标准输入读取或者是拷贝字符串时需要注意无界字符串错误问题,即程序员并不知道用户的输入是多长,所以必须要做限制。
此问题本身如果并不容易发生,但是如果长期运行可能会遇见未知的问题,并极难发现问题,因为这种字符串一旦越界本身就是将数据存到了未知的内存空间中。

1. 1 C语言中用户输入遇到的无界字符串错误问题


1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <string.h>
int main()
{
    char Passwd[20];
    puts("Enter 8 character passwd:");
    gets(Passwd);
    printf("Passwd size: %d, Passwd: %s\n", strlen(Passwd), Passwd);
    return 0;
}

注解:在以上的代码中运行并没有问题,可是看看结果就会发现存在了潜在的问题,虽然有了提示,但是用户就故意输入错误的信息,那么程序就危险了

解决方法

1. 这就需要程序员来进行边界检查了,本身如果是输入密码的话需要验证密码的争取性,只是还需要验证一下用户输入的长度即可。
2. 现在大部分软件在用户输入时都已经使用方法来限制用户输入过长的信息防止这个问题的发生。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>
int main()
{
    char Passwd[20];
    puts("Enter 8 character passwd:");
    gets(Passwd);
    if(strlen(Passwd) > 8) {
        puts("Enter 8 character beyond");
        return -1;
    }
    printf("Passwd size: %d, Passwd: %s\n", strlen(Passwd), Passwd);
    return 0;
}

运行结果:

1.2 C++用户输入无界字符串错误


1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
    char Passwd[12];
    cout << "Enter 8 character passwd:";
    cin >> Passwd;
    cout << "len: " << strlen(Passwd) << ", Passwd: " << Passwd << endl;
    return 0;
}

运行结果:这时我们发现用户想输入的内容和长度时不受你限制的,所以这就有可能造成程序的错误

解决方法:


1
2
3
4
5
6
7
8
9
int main()
{
    char Passwd[12];
    cout << "Enter 8 character passwd:";
    cin.width(12); // 限制用户输入长度
    cin >> Passwd;
    cout << "len: " << strlen(Passwd) << ", Passwd: " << Passwd << endl;
    return 0;
}

给出跟C语言判断边界类似的方法,那就是限制用户输入最大长度运行结果:这是我们发现,不管用户输入多么长的内容,做了限制之后就只取限制长度内的内容。

1.3 复制和拼接字符串无界字符串错误


1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
    char name[20];
    strcpy(name, argv[1]);
    printf("name size: %d, name: %s\n", strlen(name), name);   
    strcat(name, " = ");
    printf("name size: %d, name: %s\n", strlen(name), name);   
    strcat(name, argv[2]);
    printf("name size: %d, name: %s\n", strlen(name), name);
    return 0;
}

运行结果:

结果分析:1. 图1中正常运行没有题,图2有时也能正常运行,但是这种方法是不对的,对于这种错误也比较难以排查,只能知道有这个问题,提前做好解决方法。

解决方法

  1. 边界判断: 边界判断任然需要程序员在代码中实现,判断字符串的长度,参照获取用户输入代码
  2. 使用strcpy_s替换strcpy,strncpy_s替换strncpy,strcat_s替换strcat: strcpy_s,strncpy_s,strcat_s分别是strcpy,strncpy,strcat的安全版本,但是需要注意的是strcpy_s,strncpy_s,strcat_s等是在C++上使用的,C语言是没有这个安全版本,所以在编译的时候虽然可以编译通过,但是会有警告,并且运行时也是无法执行的。
  3. 使用动态数组的方式: 用户输入时需要首先定义一个变量,然后使用变量去接受用户的输入,所以不能使用动态数组的方式来处理。

1
2
3
4
5
6
7
8
9
10
11
12
// strcpy_s
int main(int argc, char *argv[])
{
    char name[20];
    strcpy_s(name, argv[1]);
    printf("name size: %d, name: %s\n", strlen(name), name);   
    strcat_s(name, " = ");
    printf("name size: %d, name: %s\n", strlen(name), name);   
    strcat_s(name, argv[2]);
    printf("name size: %d, name: %s\n", strlen(name), name);
    return 0;
}

运行结果:


1
2
3
4
5
6
7
8
9
10
11
12
13
// 动态数组
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // malloc头文件
int main(int argc, char *argv[])
{
    char *name = (char*)malloc(strlen(argv[1]) + 1);
    strcpy(name, argv[1]);
    printf("name size: %d, name: %s\n", strlen(name), name);
    free(name);
    name = NULL;
    return 0;
}

2. 差一错误

所谓的差一错误就是没有考虑字符串结尾处’\0’的情况

错误代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    int i = 0;
    char name[10];
    strcpy(name, "123456789"); // 没有考虑结尾处'\0'
    char *dest = (char*)malloc(strlen(name));// strlen没有考虑结尾处'\0'
    for(i = 1; i <= 11; i++) {// 数组从0开始,strlen()-1结束
         dest[i] = name[i];
    }
    dest[i] = '\0';
    printf("name len: %d, name: %s\n", strlen(name), name);
    printf("dest len: %d, dest: %s\n", strlen(dest), dest);
    // 堆空间使用完后需要释放内存
    return 0;
}

运行结果:

结果分析:在上面代码中出现了几次错误,首先就是字符串的结尾处需要考虑到给’\0’留一个字符空间;在申请动态申请内存时需要考虑到’\0’情况,需要申请strlen(s)+1的大小;循环遍历数组时需要考虑到是从0开始,strlen(s)-1结束

正确代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(int argc, char *argv[])
{
    int i = 0;
    char name[11];
    strcpy(name, "123456789");
    char *dest = (char*)malloc(strlen(name) + 1);
    for(i = 0; i < strlen(name); i++) {
        dest[i] = name[i];
    }
    dest[i] = '\0';
    printf("name len: %d, name: %s\n", strlen(name), name);
    printf("dest len: %d, dest: %s\n", strlen(dest), dest);
    free(dest);
    dest = NULL;
    return 0;
}

3. 空结尾错误

空结尾字符串通常任然是没有考虑到结尾处’\0’,

错误代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    char a[10], b[10], c[20];
    strcpy(a, "01234567ab");
    strcpy(b, "cd23456789");
    strcpy(c, a);
    strcat(c, b);
    printf("c len: %d, c: %s\n", strlen(c), c);
    return 0;
}

运行结果:

结果分析:可以看到在编译时给出警告,但是任然编译通过,而在运行时就会发现由于变量a没有’\0’结尾,导致变量a和变量b在向变量c中复制值时,变量b的内容直接覆盖了变量的内容。解决方法:这个问题相对来说是比较好解决的,要么在申请空间时申请足够多的空间,要么赋值时就要考虑到结尾’\0’的问题。

4. 字符串截断

如例子1.2中,从用户输入获取字符串信息,但是限制了输入的长度,一旦长度超过限制长度,超过长度部分的内容丢失,这就是字符串截断

5. 其他

经过以上几个例子发现,标准库函数都不靠谱,这么难的吗?到底需要怎么做?别急,就算是你自己来实现也不一定没有错误


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    int i = 0;
    char a[10];
    char *arg1 = argv[1];
    while(arg1[i] != '\0') {
        a[i] = arg1[i];
        ++i;
    }
    a[i] = '\0';
    printf("a len: %d, a: %s\n", strlen(a), a);
    return 0;
}

运行结果:

分析:程序也崩了,是不是快崩溃了,代码感觉没什么问题啊!太难了

小结:

世界上没有完美的人、事、代码,只能说具体情况具体分析,还有就是多积攒些经验就能判断你的程序需要什么解决方案。比如你要是知道了以上几种常见的错误是不是以后写代码的时候就会知道怎么做了呢。

字符串常见操作错误》有1个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注