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

MSVC与GCC编译C代码行为不一致,缓冲区溢出问题求助

LeetCode字符串翻转题跨编译器运行错误排查与修复

问题背景

用MSVC在本地编译实现的LeetCode字符串翻转题(要求翻转单词顺序并去除多余空格),本地运行所有示例都正常,复制到LeetCode的GCC编译环境后却触发运行时错误,提示缓冲区溢出。即使开启MSVC的/Wall选项消除所有警告,问题依旧存在。

核心错误点解析

  1. 初始空格处理逻辑混乱导致越界
    原代码开头处理前导空格时,仅将offset加1,后续循环里if (i == 1 && offset == 1) i = 0的逻辑会造成数组访问越界。当输入字符串以多个空格开头时,s[i + offset]会访问超出原字符串长度的内存区域,GCC的内存检测机制会直接捕捉到这个缓冲区溢出,而MSVC因内存布局或默认检测策略的差异,未触发错误。

  2. 固定长度的单词数组存在溢出风险
    原代码用WORD_LEN 20限制单词长度,但LeetCode测试用例中可能存在超过20字符的单词,写入时直接触发缓冲区溢出,这是GCC报错的关键原因之一。同时用原字符串长度作为单词数组的元素个数,完全冗余且浪费内存。

  3. 字符串清理后的终止符处理不严谨
    清理空格的循环中未正确设置字符串终止符,导致后续遍历或字符串操作读取垃圾数据,引发未定义行为。

  4. 单词回写时的边界处理错误
    最后将翻转后的单词写回原数组时,i的自增逻辑容易越界,比如在len-1位置写入空格后再覆盖为终止符,若之前已经越界写入,就会触发溢出。

修复后的代码

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

char* reverseWords(char* s) {
    int len = strlen(s);
    // 分配清理后字符串的内存(最坏情况和原字符串长度一致)
    char* cleaned = (char*)malloc(len + 1);
    if (!cleaned) {
        return NULL;
    }

    // 第一步:清理多余空格(开头、结尾连续空格,中间多个空格保留一个)
    int idx = 0;
    int i = 0;
    // 跳过开头所有空格
    while (i < len && s[i] == ' ') {
        i++;
    }
    // 处理中间内容
    for (; i < len; i++) {
        // 跳过连续空格
        if (s[i] == ' ' && s[i-1] == ' ') {
            continue;
        }
        cleaned[idx++] = s[i];
    }
    // 移除结尾可能残留的单个空格
    if (idx > 0 && cleaned[idx-1] == ' ') {
        idx--;
    }
    cleaned[idx] = '\0';
    int cleaned_len = idx;

    // 第二步:统计单词数量并分割单词
    int word_count = 0;
    for (i = 0; i < cleaned_len; i++) {
        if (cleaned[i] != ' ' && (i == 0 || cleaned[i-1] == ' ')) {
            word_count++;
        }
    }
    char** words = (char**)malloc(sizeof(char*) * word_count);
    if (!words) {
        free(cleaned);
        return NULL;
    }

    int word_idx = 0;
    int start = 0;
    for (i = 0; i <= cleaned_len; i++) {
        if (cleaned[i] == ' ' || cleaned[i] == '\0') {
            int word_len = i - start;
            words[word_idx] = (char*)malloc(word_len + 1);
            if (!words[word_idx]) {
                // 内存分配失败时清理已分配的内存
                for (int k = 0; k < word_idx; k++) {
                    free(words[k]);
                }
                free(words);
                free(cleaned);
                return NULL;
            }
            strncpy(words[word_idx], cleaned + start, word_len);
            words[word_idx][word_len] = '\0';
            word_idx++;
            // 跳过当前单词后的所有空格
            while (i < cleaned_len && cleaned[i] == ' ') {
                i++;
            }
            start = i;
        }
    }

    // 第三步:翻转单词顺序并拼接回cleaned数组
    idx = 0;
    for (i = word_count - 1; i >= 0; i--) {
        int word_len = strlen(words[i]);
        strcpy(cleaned + idx, words[i]);
        idx += word_len;
        if (i != 0) {
            cleaned[idx++] = ' ';
        }
        free(words[i]);
    }
    free(words);
    cleaned[idx] = '\0';

    // 可选:重新分配内存到实际长度以优化空间,LeetCode接受原长度的返回值
    // char* result = realloc(cleaned, idx + 1);
    // return result ? result : cleaned;
    return cleaned;
}

关键修复说明

  • 动态处理单词长度:不再用固定宏限制单词长度,而是根据实际单词长度分配内存,彻底避免缓冲区溢出。
  • 严谨的空格清理逻辑:先跳过开头所有空格,中间只保留单个空格,最后移除结尾空格,确保清理后的字符串格式正确。
  • 正确的内存管理:内存分配后做NULL检查,分配失败时清理已分配的内存,避免内存泄漏。
  • 明确的边界控制:所有数组访问都做边界检查,确保不会越界访问内存。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 05:29:51