【问题标题】:Why is malloc() allocating 2 more bytes than its supposed to?为什么 malloc() 分配的字节数比它应该分配的多 2 个字节?
【发布时间】:2021-01-28 00:07:40
【问题描述】:

我正在编写一个 c 编译器。 Flex 识别出我的字符串标记并将其发送到函数以将其存储在包含有关它的信息的 struct{} 中,但首先该字符串需要删除转义字符,即“”。这是我的代码:

char* removeEscapeChars(char* svalue)
{
    char* processedString; //will be the string with escape characters removed
    int svalLen = strlen(svalue);
    printf("svalLen (size of string passed in): %d\n", svalLen);
    printf("svalue (string passed in): %s\n", svalue);
    int foundEscapedChars = 0;
    for (int i = 0; i < svalLen;) 
    {
        if (svalue[i] == '\\') {
            //Found escaped character
            if (svalue[i+1] == 'n') {
                //Found newline character
                svalue[i] = int('\n');
            }
            else if (svalue[i+1] == '0') {
                //Found null character
                svalue[i] = int('\0');
            }
            else {
                //Any other character
                svalue[i] = svalue[i+1];
            }
            i++;
            foundEscapedChars++;
            for (int j = i; j < svalLen + 1; j++) {
                svalue[j] = svalue[j+1];
            }
        }
        else {
            i++;
        }
    }
    int newSize = svalLen - foundEscapedChars;
    processedString = (char*) malloc(newSize * sizeof(char));
    memcpy(processedString, svalue, newSize * sizeof(char));
    printf("newSize: %d\n", newSize);
    printf("processedString: %s\n", processedString);
    printf("processedString Size: %d\n", strlen(processedString));
    
    free(svalue);
    return processedString;
}

它在 99% 的时间都可以工作,但是当它在这个特定的字符串(或类似的 40 个字符的字符串)“-//W3C//DTD XHTML 1.0 Transitional//EN”上进行测试时,malloc() 似乎是为 2 个字节的字符串分配内存太大。输出如下。请注意,我在调用 malloc() 时使用了 int newSize,它说它的值是 40,然后 strlen() 返回 42。 sizeof(char) 也是 == 1。主要问题是它在字符串末尾插入垃圾字符。什么给了?

"-//W3C//DTD XHTML 1.0 Transitional//EN"
svalLen (size of string passed in): 40
svalue (string passed in) "-//W3C//DTD XHTML 1.0 Transitional//EN"
newSize: 40
processedString: "-//W3C//DTD XHTML 1.0 Transitional//EN"Z
processedString Size: 42
Line 47 Token: STRINGCONST Value: "-//W3C//DTD XHTML 1.0 Transitional//EN"Z Len: 40 Input: "-//W3C//DTD XHTML 1.0 Transitional//EN"

【问题讨论】:

  • 我不能完全遵循所有逻辑,但我不认为您的 memcpy 正在复制终止的空字符。结果,如您所见,在processedString 上调用strlen 可能会超出它。 (当您修复此问题时,请确保调整 malloc 以允许该空字符也有空间。)
  • 如果您扫描转义字符,计算您需要的数量,分配和转储偏移量,这将相当更有效。现在,每次找到转义字符时,您都会痛苦地将字符串重新洗牌。您拥有的转义字符越多,速度就越慢。
  • 我认为它只是从外部分配的随机内存......基本上你没有复制\0......这就是缓冲区溢出攻击的来源
  • 简而言之:“为什么 malloc() 分配的字节数比预期的多 2 个字节?”这不是 - 访问的字节数(至少)比您分配的多 2 个字节。
  • free() 给你的论点也是非常粗鲁的。你不拥有那个。你不能假设你甚至可以free()它。 removeEscapeChars("test") 爆炸。如有必要,让调用者处理清理工作。 不要自以为比来电者更了解。

标签: c parsing compiler-construction flex-lexer


【解决方案1】:

代码至少有这个问题:试图打印一个不是字符串的“字符串”,因为它缺少终止空字符和存储它的空间。

这会导致未定义的行为。此 UB 可能会显示为打印额外字符。

// processedString = (char*) malloc(newSize * sizeof(char));
// memcpy(processedString, svalue, newSize * sizeof(char));
processedString = malloc(newSize + 1);
memcpy(processedString, svalue, newSize);
processedString[new_Size] = 0;

可能还有其他问题。

【讨论】:

    【解决方案2】:

    这是对您的代码的修改,它采用了一种不同的、更传统的方法来处理字符串。首先从一个计算转义字符的函数开始,因为这将在下一步中很有用:

    int escapeCount(char* str) {
        int c = 0;
    
        // Can just increment and work through the string using the given pointer
        while (*str) {
            // Backslash something here
            if (*str == '\\') {
                ++str;
                ++c;
            }
    
            if (*str) {
              // Handle unmatched \ at end of string
              ++str;
            }
        }
    
        return c;
    }
    

    现在使用该信息,您可以分配正确的缓冲区大小:

    char* removeEscapeChars(char* str)
    {
        // IMPORTANT: Allocate strlen() + 1 for the NUL byte not counted
        char* result = malloc(strlen(str) - escapeCount(str) + 1);
        char* r = result;
    
        do {
            if (*str == '\\') {
                ++str;
    
                switch (*str) {
                    case 'n':
                        *r = '\n';
                        break;
                    case 'r':
                        *r = '\r';
                        break;
                    case 't':
                        *r = '\t';
                        break;
                    default:
                        *r = *str;
                        break;
                }
            }
            else {
                *r = *str;
            }
    
            if (*str) {
              ++str;
            }
    
            ++r;
        } while(*str);
    
        return result;
    }
    

    【讨论】:

    • 这是有道理的。我认为它在最后复制了 '\0',但我没有正确思考 strlen() 是如何不考虑它的。还要感谢更有效的代码。至于参数上的 free(),我通过使用 strdup(str) 作为 arg 来调用该函数,因此它正在释放字符串的副本。我不再需要按照您的方式执行此操作。谢谢!
    • 值得让您的函数的范围尽可能窄,并避免意外的副作用。此函数声称“删除转义字符”并就地修改或返回基于函数签名的副本。评论或文档会更清楚地说明它的作用。
    • 如果你想进一步改进这个,你可以用一个简单的查找表来转换那个笨重的switch,就像一个127个字符的长字符串一样,你可以使用escapeChar[n] where @987654325 @.
    猜你喜欢
    • 1970-01-01
    • 2017-07-01
    • 2021-07-19
    • 1970-01-01
    • 2023-03-31
    • 2012-08-02
    • 2017-05-30
    • 1970-01-01
    • 2016-03-15
    相关资源
    最近更新 更多