1

我有一个程序可以从可变长度字符数组的输入中反转字符串。该函数返回一个可变长度的字符数组并被打印出来。当我打印输出时,我确实得到了反转的字符串,但是在我的控制台打印中附加了垃圾字符。

就返回缓冲区而言,这是否是“合法”操作?如果不是正确的方法,有人可以批评我的代码并提出更好的替代方案吗?

谢谢。

#include <stdio.h>
#include <stdlib.h>
char *reverse_string(char *input_string);

char *reverse_string(char *input_string)
{
    int i=0;
    int j=0;
    char *return_string;
    char filled_buffer[16];
    while (input_string[i]!='\0')
        i++;
    while (i!=0)
    {
        filled_buffer[j]=input_string[i-1];
        i--;
        j++;
    }
    return_string=filled_buffer;
    printf("%s", return_string);
    return return_string;
}

int main (void) 
{
    char *returned_string;
    returned_string=reverse_string("tasdflkj");
    printf("%s", returned_string);
    return  1;
}

这是我从 Xcode 的输出 - jklfdsat\347\322̲\227\377\231\235

4

2 回答 2

4

不,在函数中返回指向本地字符串的指针是不安全的。C 不会阻止您这样做(尽管有时编译器会在您要求时警告您;在这种情况下,return_string除非您将代码更改为 ,否则局部变量会阻止它发出警告return filled_buffer;)。但这并不安全。基本上,该空间被其他函数重用,因此他们愉快地践踏了曾经整齐格式化的字符串。

你能更详细地解释这个评论吗?“不,这不安全……”

当函数返回时,局部变量(与字符串常量相反)超出范围。返回指向范围外变量的指针是未定义的行为,这是要不惜一切代价避免的事情。当你调用未定义的行为时,任何事情都可能发生——包括程序似乎可以工作——而且没有理由抱怨,即使程序重新格式化了你的硬盘驱动器。此外,不能保证相同的事情会在不同的机器上发生,甚至不能保证在当前机器上使用同一编译器的不同版本。

要么将输出缓冲区传递给函数,要么让函数用于malloc()分配内存,这些内存可以返回给调用函数并由调用函数释放。

将输出缓冲区传递给函数

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

int reverse_string(char *input_string, char *buffer, size_t bufsiz);

int reverse_string(char *input_string, char *buffer, size_t bufsiz)
{
    size_t j = 0;
    size_t i = strlen(input_string);
    if (i >= bufsiz)
        return -1;

    buffer[i] = '\0';
    while (i != 0)
    {
        buffer[j] = input_string[i-1];
        i--;
        j++;
    }
    printf("%s\n", buffer);
    return 0;
}

int main (void) 
{
    char buffer[16];
    if (reverse_string("tasdflkj", buffer, sizeof(buffer)) == 0)
        printf("%s\n", buffer);
    return 0;
}

内存分配

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

char *reverse_string(char *input_string);

char *reverse_string(char *input_string)
{
    size_t j = 0;
    size_t i = strlen(input_string) + 1;
    char *string = malloc(i);
    if (string != 0)
    {
        string[--i] = '\0';
        while (i != 0)
        {
            string[j] = input_string[i-1];
            i--;
            j++;
        }
        printf("%s\n", string);
    }
    return string;
}

int main (void) 
{
    char *buffer = reverse_string("tasdflkj");
    if (buffer != 0)
    {
        printf("%s\n", buffer);
        free(buffer);
    }
    return 0;
}

请注意,示例代码在每个格式字符串的末尾都包含一个换行符;它可以更容易地判断字符串的末端在哪里。

这是一种替代方法main(),它表明即使在多次调用reverse_string()函数后,返回的分配内存也是可以的(修改为采用 aconst char *而不是普通char *参数,但其他方面没有改变)。

int main (void) 
{
    const char *strings[4] =
    {
        "tasdflkj",
        "amanaplanacanalpanama",
        "tajikistan",
        "ablewasiereisawelba",
    };
    char *reverse[4];
    for (int i = 0; i < 4; i++)
    {
        reverse[i] = reverse_string(strings[i]);
        if (reverse[i] != 0)
            printf("[%s] reversed [%s]\n", strings[i], reverse[i]);
    }
    for (int i = 0; i < 4; i++)
    {
        printf("Still valid: %s\n", reverse[i]);
        free(reverse[i]);
    }
    return 0;
}

另外(正如pwny在我添加此注释之前在他的回答中指出的那样),您需要确保您的字符串以空值结尾。返回指向本地字符串的指针仍然不安全,即使您可能不会立即发现示例代码的问题。这说明了输出末尾的垃圾。

于 2013-05-16T03:26:50.437 回答
1

首先,返回一个指向本地的指针是不安全的。习惯用法是接收指向足够大缓冲区的指针作为函数的参数并用结果填充它。

垃圾可能是因为您没有以空值终止结果字符串。确保'\0'在末尾追加。

编辑:这是您可以使用惯用 C 编写函数的一种方式。

//buffer must be >= string_length + 1
void reverse_string(char *input_string, char* buffer, size_t string_length)
{
    int i = string_length;
    int j = 0;

    while (i != 0)
    {
        buffer[j] = input_string[i-1];
        i--;
        j++;
    }
    buffer[j] = '\0'; //null-terminate the string

    printf("%s", buffer);
}

然后,您将其称为:

#define MAX_LENGTH 16

int main()
{
    char* foo = "foo";
    size_t length = strlen(foo);
    char buffer[MAX_LENGTH];

    if(length < MAX_LENGTH)
    {
        reverse_string(foo, buffer, length);

        printf("%s", buffer);
    }
    else
    {
        printf("Error, string to reverse is too long");
    }
}
于 2013-05-16T03:29:17.130 回答