代码之家  ›  专栏  ›  技术社区  ›  JustWe

如何在C中合并字节数组

  •  2
  • JustWe  · 技术社区  · 7 年前

    我当前的concat函数:

    char* concat(char* a, int a_size,
                 char* b, int b_size) {
        char* c = malloc(a_size + b_size);
        memcpy(c, a,            a_size);
        memcpy(c + a_size, b,   b_size);
        free(a);
        free(b);
        return c;
    }
    

    但这需要额外的内存。是否可以使用 realloc 没有额外的内存空间?

    比如:

    void append(char* a, int a_size, char* b, int b_size)
    ...
    
    char* a = malloc(2);
    char* b = malloc(2);
    
    void append(a, 2, b, 2);
    //The size of a will be 4.
    
    2 回复  |  直到 7 年前
        1
  •  2
  •   Jean-François Fabre    7 年前

    是的,自从 realloc 如果新大小更大,将保留缓冲区的开始:

    char* concat(char* a, size_t a_size,
                 char* b, size_t b_size) {
        char* c = realloc(a, a_size + b_size);
        memcpy(c + a_size, b,  b_size);  // dest is after "a" data, source is b with b_size
        free(b);
        return c;
    }
    

    c a

    我的建议是警告函数的用户,必须使用 malloc ,否则会严重崩溃。

        2
  •  3
  •   Nominal Animal    7 年前

    Jean-François Fabre 在回答上述问题时,我想指出的是,通过使用以下结构,您可以更好地管理此类字节数组:

    typedef struct {
        size_t         max;  /* Number of chars allocated for */
        size_t         len;  /* Number of chars in use */
        unsigned char *data;
    } bytearray;
    #define  BYTEARRAY_INIT  { 0, 0, NULL }
    
    void bytearray_init(bytearray *barray)
    {
        barray->max  = 0;
        barray->len  = 0;
        barray->data = NULL;
    }
    
    void bytearray_free(bytearray *barray)
    {
        free(barray->data);
        barray->max  = 0;
        barray->len  = 0;
        barray->data = NULL;
    }
    

    要声明空字节数组,可以使用 bytearray myba = BYTEARRAY_INIT; bytearray myba; bytearray_init(&myba); . 两者是等价的。

    当您不再需要数组时,请调用 bytearray_free(&myba); free(NULL) 是安全的,什么也不做,所以释放一个 bytearray 已初始化但未使用的。

    附加到 bytearray公司 :

    int bytearray_append(bytearray *barray, const void *from, const size_t size)
    {
        if (barray->len + size > barray->max) {
            const size_t  len = barray->len + size;
            size_t        max;
            void         *data;
    
            /* Example policy: */
            if (len < 8)
                max = 8; /* At least 8 chars, */
            else
            if (len < 4194304)
                max = (3*len) / 2;  /* grow by 50% up to 4,194,304 bytes, */
            else
                max = (len | 2097151) + 2097153 - 24; /* then pad to next multiple of 2,097,152 sans 24 bytes. */
    
            data = realloc(barray->data, max);
            if (!data) {
                /* Not enough memory available. Old data is still valid. */
                return -1;
            }
    
            barray->max  = max;
            barray->data = data;
        }
    
        /* Copy appended data; we know there is room now. */
        memmove(barray->data + barray->len, from, size);
        barray->len += size;
    
        return 0;
    }
    

    因为这个函数至少在理论上不能重新分配内存,所以它会返回 0

    不需要一个 malloc() 打电话,因为 realloc(NULL, size) 完全等同于 malloc(size)

    “增长政策”是一个非常有争议的问题。你只需要 max = barray->len + size ,就这样结束了。但是,动态内存管理功能相对比较慢,所以在实践中,我们不想调用 realloc()

    上面的策略试图做得更好,但不要太激进:它总是分配至少8个字符,即使需要更少的字符。最多4194304个字符,分配50%的额外字符。在上面,它将分配大小四舍五入到2097152的下一个倍数,并减去24;绝对不是 “这是最好的,这也是你应该做的” . 此策略确保每个字节数组最多分配4194304=2 22 未使用的字符。然而,2097152=2 是AMD64(x86-64)上一个巨大页面的大小,是基本上所有体系结构上本机页面大小的两倍的幂。它还足够大,可以在基本上所有这样做的架构上从所谓的sbrk()分配切换到内存映射。这意味着如此巨大的分配使用堆中的一个单独的部分进行分配,而未使用的部分通常只是虚拟内存,在被访问之前不一定由任何RAM支持。因此,这项政策 倾向于

    当然,如果你 (或测量!)典型工作负载中字节数组的典型大小,您可以为此优化增长策略,并获得更好的结果。

    memmove() 而不是 memcpy() ,以防有人想重复同一字节数组的一部分: 只有在源区域和目标区域不重叠时才起作用; 即使在那种情况下也行。


    当使用更高级的数据结构(如哈希表)时,上述结构的变体通常很有用(也就是说,在有大量空字节数组的情况下,这会更好。)

    数据不是指向数据的指针,而是结构本身的一部分,作为C99灵活数组成员:

    typedef struct {
        size_t         max;
        size_t         len;
        unsigned char  data[];
    } bytearray;
    

    不能声明字节数组本身(即。 bytearray myba; 不起作用);你总是宣布 对于这样的字节数组: bytearray *myba = NULL; . 指针为NULL时,将被视为空字节数组。

    尤其要看有多少 data 对于这样的数组项,您可以使用访问器函数(也在与数据结构相同的头文件中定义),而不是 myba.len :

    static inline size_t  bytearray_len(bytearray *const barray)
    {
        return (barray) ? barray->len : 0;
    }
    
    static inline size_t  bytearray_max(bytearray *const barray)
    {
        return (barray) ? barray->max : 0;
    }
    

    这个 (expression) ? (if-true) : (if-false) 是三元运算符。在这种情况下,第一个函数与

    static inline size_t  bytearray_len(bytearray *const barray)
    {
        if (barray)
            return barray->len;
        else
            return 0;
    }
    

    如果你想知道 bytearray *const barray ,请记住指针声明是从右向左读取的 * barray

    由于这类数组通常需要调整大小,因此调整大小通常放在一个单独的助手函数中:

    bytearray *bytearray_resize(bytearray *const barray, const size_t len)
    {
        bytearray *temp;
    
        if (!len) {
            free(barray);
            errno = 0;
            return NULL;
        }
    
        if (!barray) {
            temp = malloc(sizeof (bytearray) + len * sizeof barray->data[0]);
            if (!temp) {
                errno = ENOMEM;
                return NULL;
            }
    
            temp->max = len;
            temp->len = 0;
            return temp;
        }
    
        if (barray->len > len)
            barray->len = len;
    
        if (barray->max == len)
            return barray;
    
        temp = realloc(barray, sizeof (bytearray) + len * sizeof barray->data[0]);
        if (!temp) {
            free(barray);
            errno = ENOMEM;
            return NULL;
        }
    
        temp->max = len;
        return temp;
    }
    

    那是什么意思 errno = 0 NULL 具有 errno == ENOMEM ,就像 malloc() / 去吧。但是,由于所需的新长度为零,因此通过释放旧字节数组(如果有的话)来节省内存,并返回 . 但既然这不是一个错误,我们设置 errno 无效的 ,检查 厄尔诺 . 如果 厄尔诺 strerror(errno) 以获取描述性错误消息。)

    你可能也注意到了 sizeof barray->data[0] 巴拉伊 sizeof 数据 成员,而不更改任何其他代码。

    要将数据附加到这样一个字节数组中,我们可能希望能够指定是否预期进一步附加到同一个数组,或者这是否可能是最后一次附加,以便只需要确切的所需内存量。为了简单起见,我只在这里实现精确的大小版本。请注意,此函数返回指向(修改的)字节数组的指针:

    bytearray *bytearray_append(bytearray *barray,
                                const void *from, const size_t size,
                                int exact)
    {
        size_t  len = bytearray_len(barray) + size;
    
        if (exact) {
            barray = bytearray_resize(barray, len);
            if (!barray)
                return NULL; /* errno already set by bytearray_resize(). */
    
        } else
        if (bytearray_max(barray) < len) {            
    
            if (!exact) {
    
                /* Apply growth policy */
                if (len < 8)
                    len = 8;
                else
                if (len < 4194304)
                    len = (3 * len) / 2;
                else
                    len = (len | 2097151) + 2097153 - 24;
            }
    
            barray = bytearray_resize(barray, len);
            if (!barray)
                return NULL; /* errno already set by the bytearray_resize() call */
        }
    
        if (size) {
            memmove(barray->data + barray->len, from, size);
            barray->len += size;
        }
    
        return barray;
    }
    

    这次,我们宣布 bytearray *barray ,因为我们换了地方 指向函数中的。如果第四个参数, final ,则得到的字节数组正好是所需的大小;否则将应用增长策略。