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

LeetCode 125有效回文题:代码切换后崩溃问题求助

LeetCode 125题:验证有效回文代码崩溃原因分析

问题场景

我在解决LeetCode第125题「验证有效回文」时遇到问题:启用代码中#if 0块的逻辑时程序触发**堆缓冲区溢出(heap-buffer-overflow)**崩溃,但改用#else块逻辑后代码可正常运行。需求是判断原字符串剥离出仅含[a-z][0-9]的子串是否为回文,现分析崩溃原因。

代码片段

// https://leetcode.com/problems/valid-palindrome/

// Leetcode-125

// Helperfunction to find if a palindrome
bool isPal(char *s , int count)
{
    int iterations = (count/2)-1;
    
    for (int i = 0; i <= iterations; i++)
    {
        if (s[i] != s[count-1-i])
        {
            return false;
        }
    }
    return true;
}

// function that Leetcode's main calls.
bool isPalindrome(char * s) 
{
    int i = 0, j=0;
    int count = 0;
    
    if (!s)
        return false;
    
// Count number of valid characters. Replace invalid characters with 0xff.
// Change Upper to lower case numbers
    while (s[i] != '\0')
    {
    // [a-z][0-9]
        if ((s[i] >= '0' && s[i] <='9') ||
           (s[i] >= 'a' && s[i] <='z'))
           {
               count++;
           }
           else
           {
               if ((s[i] >= 'A' && s[i] <='Z'))
               {
                   count++;
                   s[i] = s[i] + ('a' - 'A');
               }
               else
               {
                   s[i] = 0xff;
               }
           }
           i++;
    }
    
    if (!count)
        return true;
           
    char *stripped = (char *) malloc ((count+1) * sizeof(char));
    i = 0;
#if 0  
// Enabling this causes a crash         
    while(s[i] != '\0')
    {
        if(s[i] != 0xff)
        {
            stripped[j] = s[i];
            j++;
        }
        i++;
    }
#else
    while(s[i] != '\0')
    {
        if ((s[i] >= '0' && s[i] <='9') ||
           (s[i] >= 'a' && s[i] <='z'))
           {
                stripped[j] = s[i];
                j++;
           }
           i++;
    }    
#endif

   stripped[count] = '\0';
    
    return isPal(stripped, count);
}

崩溃原因分析

核心问题:修改只读内存引发未定义行为

LeetCode传入的字符串参数大概率是只读内存(如字符串字面量),这类内存存储在程序的只读数据段,不允许修改。但代码的第一个循环中存在两处修改原字符串的操作:

  1. 将大写字母转换为小写时,直接修改s[i]
  2. 将无效字符设为0xff时,直接修改s[i]

这种修改只读内存的行为属于未定义行为,可能会破坏内存结构,导致后续对堆内存stripped的写入操作触发堆缓冲区溢出。

为什么#else块暂时正常?

#else块的逻辑虽然也依赖修改后的s[i],但可能因测试用例的特殊性(如输入字符串不在只读内存)或编译器优化,暂时未触发崩溃,但本质上这种修改原字符串的做法仍然是不安全的,存在潜在风险。

次要风险:0xff的符号歧义

若编译器将char视为有符号类型,0xff会被解释为-1。如果原字符串中存在值为-1的扩展ASCII字符,会被误判为无效字符,但这不是本次崩溃的直接原因,只是代码的潜在漏洞。

修复方案

避免修改原字符串,改为在复制时直接判断并转换字符:

  1. 统计有效字符数量时,仅计数不修改原字符串
  2. 复制有效字符时,实时判断字符类型并转换为小写

修改后的核心代码片段:

// 仅统计有效字符数量,不修改原字符串
while (s[i] != '\0') {
    if ((s[i] >= '0' && s[i] <= '9') || 
        (s[i] >= 'a' && s[i] <= 'z') || 
        (s[i] >= 'A' && s[i] <= 'Z')) {
        count++;
    }
    i++;
}

// 复制并转换有效字符
i = 0;
j = 0;
while (s[i] != '\0') {
    if (s[i] >= '0' && s[i] <= '9') {
        stripped[j++] = s[i];
    } else if (s[i] >= 'a' && s[i] <= 'z') {
        stripped[j++] = s[i];
    } else if (s[i] >= 'A' && s[i] <= 'Z') {
        stripped[j++] = s[i] + ('a' - 'A');
    }
    i++;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 20:15:42