为什么如果我使用Visual C(Visual Sudio 2022),此代码可以按预期工作,但如果我使用gcc 8.2,则无法正常工作?

umuewwlo  于 2023-08-03  发布在  其他
关注(0)|答案(3)|浏览(134)

我是C的新手,还在学习(目前正在做一些“LeetCode”的挑战)。我写了一个函数,它应该返回字符串数组中最长的公共前缀。我用几个不同的值进行了测试,一切正常。但是字符串数组{"aaa", "aa", "aaa"}在Visual Studio中会导致“aa”(正确),但在gcc版本中会导致“aaa”,我不明白为什么。
100d 1xx 1c 1d 1x的字符串

我的代码如下:

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

char* longestCommonPrefix(char** strs, int strsSize) {

    char* result = calloc(200, sizeof(char));
    if (result == NULL) return NULL;
    size_t result_length = strlen(strs[0]);
    memcpy(result, strs[0], result_length);
    char* result_end = result + result_length + 1;

    for (size_t i = 1; i < strsSize; ++i) {
        char* ptr_res = result;
        char* ptr_cur = strs[i];

        while (*ptr_res == *ptr_cur && *ptr_res > '\0' && *ptr_cur > '\0') {
            ++ptr_res;
            ++ptr_cur;
        }

        result_end = ptr_res;
    }
    *result_end = '\0';
    return result;
}

int main(int argc, char* argv[]) {
    char* a[3];
    a[0] = "aaa";
    a[1] = "aa";
    a[2] = "aaa";

    char* b = longestCommonPrefix(a, 2);
    printf(b);
    free(b);
}

字符串
@Chris; @EricPostpischil;@VladFromMoscow;
谢谢你指出我的缺点。我从你们身上学到了很多。我将@EricPostpischil的答案标记为解决方案,因为我测试了它,它适用于这个挑战。@VladFromMoscow的答案很棒,对于我实际部署的代码来说,它似乎要好得多,所以我一定会做笔记。
这里是我提交给LeetCode的工作代码,它运行得很好:

char* longestCommonPrefix(char** strs, int strsSize) {

    char* result = calloc(200, sizeof(char));
    if (result == NULL) return NULL;
    size_t result_length = strlen(strs[0]);
    memcpy(result, strs[0], result_length);
    char* result_end = result + result_length;

    for (size_t i = 1; i < strsSize; ++i) {
        char* ptr_res = result;
        char* ptr_cur = strs[i];

        while (*ptr_res == *ptr_cur && ptr_res < result_end) {
            ++ptr_res;
            ++ptr_cur;
        }

        result_end = ptr_res;
    }
    *result_end = '\0';
    return result;
}

xxb16uws

xxb16uws1#

这是错误的:

char* result_end = result + result_length + 1;

字符串
result_length包含了strs[0]中空字符之前的字符数,result_end用于设置后面的空字符,所以它应该指向空字符将去的地方:

char* result_end = result + result_length;


然后这段代码:

while (*ptr_res == *ptr_cur && *ptr_res > '\0' && *ptr_cur > '\0') {


允许循环进行,直到当前在result缓冲区中的字符串或正在与之进行比较的当前字符串结束。这可以比迄今为止建立的最长公共前缀更远。循环的长度应限制为最长的公共前缀:

while (*ptr_res == *ptr_cur && ptr_res < result_end) {


注意,不需要测试空/非空字符。如果在ptr_cur中遇到空字符,则*ptr_cur*ptr_res不同,或者我们已经到达result中的字符串结尾,result_end已经指向(最初)或之前(如果它是从先前的比较中更新的)。
此外,*ptr_res > '\0'是测试非空字符的不正确方法,因为字符值可以为负。*ptr_res != '\0'是一个正确的测试。
printf(b);是危险的,因为如果对b处理不当,它很容易出现不当行为。此外,通常最好用换行符终止程序输出。使用puts(b)printf("%s\n", b);

ippsafx7

ippsafx72#

在你的代码中有两个问题跳出来。
首先,您调用了longestCommonPrefix,其中2而不是3是输入数组的长度。
第二,在循环中,您实际上从未通过编写新的空终止符来修改result。您只需在最后一次for循环迭代后解引用result_end并为其分配null终止符。这样做的效果是只考虑数组中复制到result的第一个字符串,以及根据传入的长度将其视为数组中的“最后一个”字符串。
如果用2调用该函数,则输出“aa”。如果你用3调用它,它会考虑最后一个字符串文字"aaa",并发现在"aaa""aaa"之间,"aaa"是最长的公共前缀。
清理一些其他的东西:

char* longestCommonPrefix(char **strs, int strsSize) {
    char *result = calloc(200, sizeof(char));
    if (result == NULL) return NULL;

    size_t result_length = strlen(strs[0]);
    memcpy(result, strs[0], result_length);

    for (size_t i = 1; i < strsSize; ++i) {
        char *ptr_res = result;
        char *ptr_cur = strs[i];

        while (*ptr_res == *ptr_cur && *ptr_res && *ptr_cur) {
            ++ptr_res;
            ++ptr_cur;
        }

        *ptr_res = '\0';
    }

    return result;
}

字符串

ujv3wf0j

ujv3wf0j3#

首先,您声明了一个包含3个指向字符串文字的指针的数组:

char* a[3];
a[0] = "aaa";
a[1] = "aa";
a[2] = "aaa";

字符串
但不清楚为什么要传递此调用的第二个参数:

char* b = longestCommonPrefix(a, 2);


等于2而不是3
另一个问题是在此声明的初始化程序中使用了幻数200

char* result = calloc(200, sizeof(char));


在函数longestCommonPrefix中。
一般情况下采用以下语句:

size_t result_length = strlen(strs[0]);
memcpy(result, strs[0], result_length);


可能会导致未定义的行为,因为指向数组元素的字串可能会大于幻数200
调用memcpy存储字符后指针result指向的动态分配数组不包含字符串。
这种内存分配完全没有意义,因为传递的数组可以包含指向空字符串的指针。
变量result_end的初始化器也是:

char* result_end = result + result_length + 1;


同样没有意义的还有再次可以调用未定义的行为归于语句:

*result_end = '\0';


因为指针result_end不指向存储器中复制的串的最后一个元素之后。即数组将包含不正确的数据。至少你应该写

char* result_end = result + result_length;


这个while循环

while (*ptr_res == *ptr_cur && *ptr_res > '\0' && *ptr_cur > '\0') {
        ++ptr_res;
        ++ptr_cur;
    }


如果指针ptr_cur所指向的字符串比指针result所指向的已存储字符序列短,则可以保持动态分配数组result的内容不变。
而for循环的效率很低,因为没有检查是否已经遇到了空字符串。
请注意,该函数是不安全的,因为第二个参数可以是非正值。
甚至这个叫printf

printf(b);


如果公共前缀包含形成格式规范的符号,则通常可以调用未定义的行为。
首先,您需要找到公共前缀的长度。并且只有在此之后才能分配所需大小的数组,并将前缀作为字符串复制到数组中。
函数和程序整体应如下所示

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

char * longestCommonPrefix( char **strs, size_t strsSize )
{
    size_t commonPrefixSize = 0;
    char *result = NULL;

    if (strsSize != 0)
    {
        commonPrefixSize = strlen( strs[0] );

        for (size_t i = 1; commonPrefixSize != 0 && i < strsSize; i++)
        {
            size_t n = 0;

            while ( n < commonPrefixSize && strs[i][n] != '\0' && strs[i][n] == strs[0][n]) ++n;

            if (n < commonPrefixSize) commonPrefixSize = n;
        }

        result = malloc( commonPrefixSize + 1 );

        if (result != NULL)
        {
            result[commonPrefixSize] = '\0';
            memcpy( result, strs[0], commonPrefixSize );
        }
    }

    return result;
}

int main( void )
{
    char *strs[] = { "aaa", "aa", "aaa" };
    size_t N = sizeof( strs ) / sizeof( *strs );

    char *commonPrefix = longestCommonPrefix( strs, N );

    if (commonPrefix != NULL)
    {
        printf( "\"%s\"\n", commonPrefix );
    }

    free( commonPrefix );
}


程序输出为:

"aa"

相关问题