【问题标题】:function in c referencing memory externally allocatedc中的函数引用外部分配的内存
【发布时间】:2021-04-08 02:16:41
【问题描述】:

我正在拆分这些行,因为它们在输入中是红色的:

char **split (char *in, size_t *n, char *delimiter){
    if (delimiter == NULL) delimiter="\t"; // default
    
    char **tokens = malloc(20 * sizeof(char*));

    char *token = strtok(in, delimiter);
    while (token != NULL) {
        tokens[*n] = malloc(30 * sizeof(char*)); // <- why not *tokens[0]
        strcpy(tokens[*n], token);
        (*n)++;
        token = strtok(NULL, "\t");
    }
    return tokens;
}

我有一个(可能是错误的,但请向我解释)想法,即在每一行上分配(和释放)不同的内存插槽效率不高。

在拆分函数的外部分配内存并将指向字符串数组的指针传递给拆分函数是否安全(并且可能?)?如何(然后我会在下一行之前使用 memset)?

谢谢!

【问题讨论】:

  • 我有一个(可能是错的,但请向我解释)想法,即在每一行上分配(和释放)不同的内存插槽效率不高。 那么如果它是?您是否有任何证据表明这种低效率会造成任何问题?您可能出于某种原因购买了 CPU 及其提供的周期。如果这意味着您以牺牲一些效率为代价编写易于阅读、易于维护的代码,那么这就是使用快速 CPU 的一个很好的理由。

标签: c function malloc


【解决方案1】:

您遇到的主要问题是您在循环中为 30 个指针分配空间,然后将字符串复制到该空间中。如果该字符串恰好超过 30 个指针(包括终止的 NUL),您将超出分配的空间并损坏内存。如果它更短,你只是在浪费一些空间。

最好为字符串分配足够的空间:

tokens[*n] = malloc(strlen(token) + 1);

或者如果您有可用的strdup (POSIX),您可以使用它同时执行 malloc 和 strcpy

tokens[*n] = strdup(token);

您还有一个问题,即您在初始分配中只为 20 个令牌(指针)分配空间——如果您有超过 20 个,您也会超出该空间。最好是realloc 指针数组根据需要使其变大。

【讨论】:

    【解决方案2】:

    在拆分函数的外部分配内存并将指向字符串数组的指针传递给拆分函数是否安全(并且可能?)?

    不,因为在阅读之前您不知道每个令牌有多大,也不知道有多少令牌。因此,您编写的代码是不安全的。它假设每个令牌不超过 29 个字符,并且不超过 20 个令牌。


    使用strdupas Chris Dodd explains 可以解决为每个令牌分配恰到好处的内存量。但这仍然会让你分配一堆小块内存。

    为了最大限度地减少分配,您可以将所有令牌存储在单个内存块中。这就是strtok 所做的;它修改输入字符串,用空值替换分隔符。 tokens 不会复制每个标记,而是简单地指向原始字符串中每个标记的开头。

    这意味着tokens 指向in,而你的函数正在修改in。我会选择是否将输入字符串复制给调用者。

    // Don't modify the original, tokens points at a copy.
    char **tokens = split(strdup(input), &num_tokens, "\t");
    
    // Modify the original, tokens points at input.
    char **tokens = split(input, &num_tokens, "\t");
    

    这就留下了分配tokens 的问题。为此,您要么需要知道有多少令牌,要么即时重新分配。

    要最小化 CPU,您可以根据需要将 realloc 增加到 tokens

        size_t tokens_size = 1;
        char **tokens = malloc(tokens_size * sizeof(char*));
    
        *num_tokens = 0;
        for(
            char *token = strtok(in, delimiter);
            token;
            token = strtok(NULL, delimiter)
        ) {
            if( *num_tokens >= tokens_size ) {
                tokens_size *= 2;
                tokens = realloc(tokens, tokens_size * sizeof(char*));
            }
    
            tokens[*num_tokens] = token;
    
            (*num_tokens)++;
        }
    

    作为效率和内存使用之间的折衷方案,我没有为每个令牌重新分配,而是将每个重新分配的令牌大小加倍。这将使用额外的内存,但会最大限度地减少重新分配。


    为了最大限度地减少分配,您需要对字符串进行标记并记住有多少标记。然后分配足够的空间。然后再次读取存储指向标记的指针的字符串。

        // tokenize in and remember how many there are.
        *num_tokens = 0;
        for(
            char *token = strtok(in, delimiter);
            token;
            token = strtok(NULL, delimiter)
        ) {
            (*num_tokens)++;
        }
    
        // Allocate exactly enough space.
        char **tokens = malloc(*num_tokens * sizeof(char*));
        
        // Iterate through the tokens and store them.
        char *token = in;
        for(size_t i = 0; i < *num_tokens; i++) {
            // Store a pointer to the token
            tokens[i] = token;
            // Jump ahead the length of the token plus the null.
            token += strlen(token) + 1;
        }
    

    对于扫描输入两次的成本,您可以只执行一次 malloc。


    注意:如果结构体包含所有信息,这段代码会更简单、更安全。

    typedef struct {
        char *original;
        char **tokens;
        size_t num_tokens;
        size_t tokens_size;
    } Tokens;
    

    这将所有内容组合在一起,您可以将其分解为 Tokens_initTokens_free 等函数。

    【讨论】:

    • 非常感谢!在第二个选项中,给定令牌的确切数量和每个令牌的 strlen 的分配,就不需要 memset,对吧?
    • @aerijman 正确。您没有复制任何内容,只是对指针列表进行排序。
    猜你喜欢
    • 2013-10-20
    • 1970-01-01
    • 2013-10-31
    • 1970-01-01
    • 2016-07-29
    • 2012-11-06
    • 2012-06-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多