strcat 上的分段错误

Segmentation fault on strcat

最近开始学习C语言,多次运行报错,从<string.h>模块调用strcat函数导致段错误。我在网上搜索了答案,包括 this Whosebug post,但没有成功。我认为这个社区可能对这个问题有更个人化的见解,因为一般的解决方案似乎并不奏效。可能是用户错误,可能是代码的个人问题。看一看。

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

char * deblank(const char str[]){
    char *new[strlen(str)];
    char *buffer = malloc(strlen(new)+1);
    for (int i=0; i<strlen(*str); i++){
        if(buffer!=NULL){
            if(str[i]!=" "){
                strcat(new,str[i]); //Segmentation fault
            }
        }
    }
    free(buffer);
    return new;
}

int main(void){
    char str[] = "This has spaces in it.";
    char new[strlen(str)];
    *new = deblank(str);
    puts(new);
}

我已经在我追踪到分段错误的行上发表了评论。以下是一些 Java 使此 C 代码有意义的内容。

public class deblank {
    public static void main(String[]args){
        String str = "This has space in it.";
        System.out.println(removeBlanks(str));
    }

    public static String removeBlanks(String str){
        String updated = "";
        for(int i=0; i<str.length(); i++){
            if(str.charAt(i)!=' '){
                updated+=str.charAt(i);
            }
        }
        return updated;
    }
}

任何对此错误的见解将不胜感激。也请指出错别字……众所周知,我会犯错。谢谢

所以,不确定这是否对您有帮助,但是与您的 Java 代码执行相同操作的 C 代码如下所示:

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

static char *removeBlanks(const char *str)
{
    char *result = malloc(strlen(str) + 1);
    if (!result) exit(1);
    const char *r = str;
    char *w = result;
    while (*r)
    {
        // copy each character except when it's a blank
        if (*r != ' ') *w++ = *r;
        ++r;
    }
    *w = 0; // terminate the result to be a string (0 byte)
    return result;
}

int main(void)
{
    const char *str = "This has spaces in it.";
    char *new = removeBlanks(str);
    puts(new);
    free(new);
    return 0;
}

我不建议命名变量 new ...如果您想使用 C++,这是一个保留关键字。

我尝试在启用警告的情况下进行编译,这里有一些您应该修复的问题。

  • 您需要包括 stdlib.h
  • char *new[strlen(str)] 创建一个 char* 而不是 char 的数组,所以不是真正的字符串。将其更改为 char new[strlen(str)].
  • 要检查 str[i] 是否为 space,您将其与 space 字符 ' ' 进行比较,而不是将其与唯一字符为 [=50= 的字符串进行比较] " "。所以将其更改为 str[i]!=' '
  • strcat 将字符串作为第二个参数而不是字符,就像您用 str[i].
  • 给它一样

另外,你用 buffer 做什么?

另一个错误是,您可能假设未初始化的数组取零值。 new 数组具有随机值,而不是 zero/null。 strcat 连接两个字符串,因此它会尝试将字符串放在第一个参数 new 末尾的第二个参数中。字符串的 "end" 是空字符。该程序搜索 new 它可以找到的第一个空字符,当它找到这个空字符时,它开始从那里写入第二个参数。

但由于new未初始化,程序可能无法在new中找到空字符,并且会继续搜索超过new、[=29=的长度],继续在未分配的内存中搜索。这可能是导致分段错误的原因。

您不能像以前那样使用 strcat,它的目的是在另一个给定字符串的末尾连接一个 C 字符串。 str[i] 是一个字符而不是 C 字符串(请记住,C 字符串是一个连续的字符序列,最后一个是 NUL 字节)。

你也不能用标准的比较运算符比较字符串,如果你真的需要比较字符串,那么有一个 strcmp 函数。但是您可以将 char 与标准运算符进行比较,因为 char 只是一种整数类型。

这应该可以解决问题:

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

char * deblank(const char str[]) {
    char *buffer = malloc(strlen(str)+1); // allocate space to contains as much char as in str, included ending NUL byte
    for (int i=0, j=0; i<strlen(str)+1; i++) { // for every char in str, included the ending NUL byte
      if (str[i]!=' ') { // if not blank
        buffer[j++] = str[i]; // copy
      }
    }
    return buffer; // return a newly constructed C-string
}

int main(void){
    char str[] = "This has spaces in it.";
    char *new = deblank(str);
    puts(new);
    free(new); // release the allocated memory
}

好的,我们开始吧。

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

char * deblank(const char str[]){
    char *new[strlen(str)];

^ 这一行创建了一个指针数组,而不是一个字符串。

    char *buffer = malloc(strlen(new)+1);

malloc 未申报。缺少 #include <stdlib.h>。另外,你应该在这里检查分配失败。

strlen(new) 是类型错误。 strlen 需要一个 char *new 是(或者更确切地说计算为)一个 char **.

    for (int i=0; i<strlen(*str); i++){

strlen(*str) 是类型错误。 strlen 采用 char **strchar(即单个字符)。

i<strlen(...) 值得怀疑。 strlen returns size_t (无符号类型)而 iint (有符号,可能太小)。

在循环中调用 strlen 是低效的,因为它必须遍历整个字符串才能找到结尾。

        if(buffer!=NULL){

这是一个检查分配失败的奇怪地方。此外,您不会在任何地方使用 buffer,那么为什么 create/check 呢?

            if(str[i]!=" "){

str[i]!=" " 是类型错误。 str[i]char" " 是(或者更确切地说是计算为)char *.

                strcat(new,str[i]); //Segmentation fault

这是类型错误。 strcat 接受两个字符串 (char *),但 newchar **str[i]char。此外,strcat 的第一个参数必须是有效字符串,但 new 未初始化。

            }
        }
    }
    free(buffer);
    return new;

new是这个函数中的局部数组。您正在 returning 它的第一个元素的地址,这是没有意义的:一旦函数 returns,它的所有局部变量都消失了。您在此处return输入了一个无效指针。

此外,这是一个类型错误:deblank 声明为 return a char * 但实际上 returns a char **.

}

int main(void){
    char str[] = "This has spaces in it.";
    char new[strlen(str)];
    *new = deblank(str);

这是类型错误:*newchardeblank return 是 char *.

    puts(new);

puts 接受一个字符串,但 new 在这一点上本质上是垃圾。

}

可以通过三种方法完成任务。

第一个是更新字符串"in place"。在这种情况下,该函数可以类似于以下方式

#include <stdio.h>
#include <ctype.h>
#include <iso646.h>

char * deblank( char s[] )
{
    size_t i = 0;

    while ( s[i] and not isblank( s[i] ) ) ++i;

    if ( s[i] )
    {
        size_t j = i++;
        do
        {
            if ( not isblank( s[i] ) ) s[j++] = s[i];
        } while( s[i++] );
    }

    return s;
}

int main(void) 
{
    char s[] = "This has spaces in it.";

    puts( s );

    puts( deblank( s ) );

    return 0;
}

程序输出为

This has spaces in it.
Thishasspacesinit.

另一种方法是将源字符串复制到目标字符数组中,跳过空格。

在这种情况下,该函数将有两个参数:源数组和目标数组。并且目标数组的大小必须等于源数组的大小,因为通常源数组不能有空格。

#include <stdio.h>
#include <ctype.h>
#include <iso646.h>

char * deblank( char *s1, const char *s2 )
{
    char *t = s1;

    do 
    {
        if ( not isblank( *s2 ) ) *t++ = *s2;
    } while ( *s2++ );

    return s1;
}

int main(void) 
{
    char s1[] = "This has spaces in it.";
    char s2[sizeof( s1 )];

    puts( s1 );

    puts( deblank( s2, s1 ) );

    return 0;
}

程序输出与上图相同。

注意这个声明

char s2[sizeof( s1 )];

目标字符串的大小一般应不小于源字符串的大小。

最后第三种方法是在函数内部动态创建一个数组,并从函数返回指向数组第一个元素的指针。

在这种情况下,最好首先计算源数组中的空白数量,以分配适当大小的目标数组。

要使用函数 mallocfree,您需要包含以下内容 header

#include <stdlib.h>

如演示程序所示,可以实现该功能

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <iso646.h>


char * deblank( const char *s )
{
    size_t n = 1;   /* one byte reserved for the terminating zero character */

    for ( const char *t = s; *t; ++t )
    {
        if ( not isblank( *t ) ) ++n;
    }

    char *s2 = malloc( n );

    if ( s2 != NULL )
    {
        char *t = s2;       
        do 
        {
            if ( not isblank( *s ) ) *t++ = *s;
        } while ( *s++ );
    }

    return s2;
}

int main(void) 
{
    char s1[] = "This has spaces in it.";
    char *s2 = deblank( s1 );

    puts( s1 );
    if ( s2 ) puts( s2 );

    free( s2 );

    return 0;
}

程序输出与前两个程序相同。

至于标准的C函数strcat那么它cat两个字符串。

例如

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

int main(void) 
{
    char s1[12] = "Hello ";
    char *s2 = "World";

    puts( strcat( s1, s2 ) );

    return 0;
}

目标数组(在本例中为 s1)必须有足够的 space 才能追加字符串。

C 标准中还有另一个 C 函数 strncat,它允许将单个字符附加到字符串。例如上面的程序可以改写成下面的方式

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

int main(void) 
{
    char s1[12] = "Hello ";
    char *s2 = "World";

    for ( size_t i = 0; s2[i] != '[=17=]'; i++ )
    {
        strncat( s1, &s2[i], 1 );
    }

    puts( s1 );

    return 0;
}

但是对您的原始任务使用这种方法效率不高,因为每次调用该函数时,它都必须在源字符串中找到要附加字符的终止零。

你可以递归试试

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

void deblank(const char* str, char *dest) {
    if (!*str) {*dest = '[=10=]';return;}

    // when we encounter a space we skip
    if (*str == ' ') {
      deblank(str+1, dest);
      return;
    }

    *dest = *str;
    deblank(str+1, dest+1);
}

int main(void) {
    const char *str = "This has spaces in it.";
    char *output    = malloc(strlen(str)+1);
    deblank(str, output);
    puts(output);
    free(output);
}