You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复看似不相关的Valgrind条件跳转与内存写入错误?

问题:Semidrome判断程序的Valgrind错误修复

我正在编写一个C程序,用于判断给定字符串是否为semidrome(由两个及以上回文拼接而成的字符串)。程序功能正常,但始终出现无法理解的Valgrind错误。我刚接触C语言和Valgrind,虽大致了解错误含义,但不清楚这些错误在代码中的触发原因。以下是原始代码、Valgrind报错信息,以及对应的错误分析和修复方案。

原始代码

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

// 判断字符串是否为回文
int isPalindrome(char str[]) {
    int len = strlen(str);
    if (len == 1){return 0;}
    int left = 0;
    int right = len - 1;
    while (right > left) {
        if (str[left++] != str[right--]) {
            return 0;
        }
    }
    return 1;
}

bool is_semidrome(char *s) {
    int len = strlen(s);
    // 空字符串不是semidrome
    if(len == 0){return 0;}
    // 本身是回文则直接是semidrome
    if(isPalindrome(s)){return 1;}
    // 用于存储子串的指针
    char *palcheck, *palcheckrest;
    for (int i = 1; i < len; i++){
        // 找到与首字符相同的位置,尝试分割出回文子串
        if (s[0] == s[i]){
            // 分配内存存储前半部分子串
            palcheck = malloc((i+2)*sizeof(char));
            palcheck = strncpy(palcheck, s, i+1);
            palcheck[i+1] = '\0';
            // 如果前半部分是回文,递归检查剩余部分是否为semidrome
            if (isPalindrome(palcheck)){
                // 分配内存存储剩余子串
                palcheckrest = malloc((len - i)*sizeof(char));
                for (int j = 0; j < (len - i - 1); j++){
                    palcheckrest[j] = s[j+i+1];
                }
                palcheckrest[len - i] = '\0';
                // 剩余部分是semidrome则返回true
                if (is_semidrome(palcheckrest)){
                    free(palcheck);
                    free(palcheckrest);
                    return true;
                }
                else{
                    free(palcheck);
                    free(palcheckrest);
                    // 如果是最后一个索引仍未找到,返回false
                    if(i == len){
                        return false;
                    }
                    // 否则继续循环
                    else{
                        free(palcheck);
                        free(palcheckrest);
                        continue;
                    }
                }
            }
            else {
                free(palcheck);
                continue;
            }
        }
    }
}

Valgrind报错信息(翻译后)

输入: popeye
==152438== Memcheck,内存错误检测器
==152438== 版权 (C) 2002-2017,Julian Seward等人,遵循GNU GPL协议
==152438== 使用Valgrind-3.15.0和LibVEX;重新运行加-h查看版权信息
==152438== 命令: ./student_answer
==152438== 
==152438== 大小为1的非法写入
==152438==    位于 0x10A3CE: is_semidrome (semidrome.c:39)
==152438==    由 0x10A1EB: main (main.c:11) 调用
==152438==  地址0x4a73154是大小为4的分配块之后的0字节位置
==152438==    位于 0x483B7F3: malloc (在/usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so中)
==152438==    由 0x10A377: is_semidrome (semidrome.c:35) 调用
==152438==    由 0x10A1EB: main (main.c:11) 调用
==152438== 
==152438== 条件跳转或移动依赖于未初始化的值
==152438==    位于 0x483EF58: strlen (在/usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so中)
==152438==    由 0x10A2B4: is_semidrome (semidrome.c:23) 调用
==152438==    由 0x10A3DC: is_semidrome (semidrome.c:40) 调用
==152438==    由 0x10A1EB: main (main.c:11) 调用
==152438== 
==152438== 条件跳转或移动依赖于未初始化的值
==152438==    位于 0x483EF58: strlen (在/usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so中)
==152438==    由 0x10A230: isPalindrome (semidrome.c:9) 调用
==152438==    由 0x10A2D3: is_semidrome (semidrome.c:25) 调用
==152438==    由 0x10A3DC: is_semidrome (semidrome.c:40) 调用
==152438==    由 0x10A1EB: main (main.c:11) 调用
==152438== 
==152438== 
==152438== 堆摘要:
==152438==     退出时正在使用: 0字节,0块
==152438==   总堆使用: 5次分配,5次释放,分配了8250字节
==152438== 
==152438== 所有堆块均已释放——无内存泄漏可能
==152438== 
==152438== 使用--track-origins=yes查看未初始化值的来源
==152438== 要查看检测到的和被抑制的错误列表,请重新运行时加: -s
==152438== 错误摘要: 3个上下文共3个错误 (已抑制: 0个,共0个)

错误分析与修复

1. 非法写入(Invalid write of size 1)

原因:第39行palcheckrest[len - i] = '\0';越界写入。palcheckrest分配的大小是len - i字节,合法索引范围是0 ~ (len - i - 1),写入len - i位置超出了分配的内存块。

修复:将该行改为:

palcheckrest[len - i - 1] = '\0';

2. 未初始化值导致的条件跳转

原因:palcheckrest的字符串未正确终止,加上之前的越界写入,导致strlen读取到未初始化的内存区域。

验证修复:修改'\0'的写入位置后,palcheckrest会被正确终止,strlen就能正常工作。

3. 重复释放内存(潜在问题)

原因:在else分支中,已经执行了free(palcheck); free(palcheckrest);,后续又重复执行一次,会导致双重释放错误。

修复:删除重复的free语句,修改后的分支代码:

else{
    free(palcheck);
    free(palcheckrest);
    // 如果是最后一个索引仍未找到,返回false
    if(i == len - 1){
        return false;
    }
    // 否则继续循环
    else{
        continue;
    }
}

4. 函数缺少返回值(潜在未定义行为)

原因:is_semidrome函数如果循环结束后未找到符合条件的分割方式,没有明确的return语句,会返回随机值,导致未定义行为。

修复:在函数末尾添加return false;,表示未找到符合条件的回文拼接。

5. 无效的索引判断(潜在逻辑问题)

原因:原代码中if(i == len)永远不会成立,因为循环条件是i < len,i的最大值是len-1。

修复:将判断改为i == len -1。

修复后的完整代码

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

// 判断字符串是否为回文
int isPalindrome(char str[]) {
    int len = strlen(str);
    if (len == 1){return 0;}
    int left = 0;
    int right = len - 1;
    while (right > left) {
        if (str[left++] != str[right--]) {
            return 0;
        }
    }
    return 1;
}

bool is_semidrome(char *s) {
    int len = strlen(s);
    // 空字符串不是semidrome
    if(len == 0){return false;}
    // 本身是回文则直接是semidrome
    if(isPalindrome(s)){return true;}
    // 用于存储子串的指针
    char *palcheck, *palcheckrest;
    for (int i = 1; i < len; i++){
        // 找到与首字符相同的位置,尝试分割出回文子串
        if (s[0] == s[i]){
            // 分配内存存储前半部分子串
            palcheck = malloc((i+2)*sizeof(char));
            strncpy(palcheck, s, i+1);
            palcheck[i+1] = '\0';
            // 如果前半部分是回文,递归检查剩余部分是否为semidrome
            if (isPalindrome(palcheck)){
                // 分配内存存储剩余子串
                palcheckrest = malloc((len - i)*sizeof(char));
                for (int j = 0; j < (len - i - 1); j++){
                    palcheckrest[j] = s[j+i+1];
                }
                palcheckrest[len - i - 1] = '\0';
                // 剩余部分是semidrome则返回true
                if (is_semidrome(palcheckrest)){
                    free(palcheck);
                    free(palcheckrest);
                    return true;
                }
                else{
                    free(palcheck);
                    free(palcheckrest);
                    // 如果是最后一个索引仍未找到,返回false
                    if(i == len - 1){
                        return false;
                    }
                    // 否则继续循环
                    else{
                        continue;
                    }
                }
            }
            else {
                free(palcheck);
                continue;
            }
        }
    }
    // 所有分割方式都尝试过,返回false
    return false;
}

内容的提问来源于stack exchange,提问作者Veronika

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 03:42:05