c - 如何在 C 中连接字节数组
问题描述
我当前的 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.
解决方案
虽然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
您已初始化但未使用的 a 是完全安全的。
附加到 a 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 个字符,即使需要的字符更少。最多 4,194,304 个字符,它会额外分配 50%。在此之上,它将分配大小四舍五入为 2,097,152 的下一个倍数并减去 24。这背后的原因很复杂,但更多的是为了说明和理解;这绝对不是“这是最好的,这也是你应该做的”。该策略确保每个字节数组最多分配 4,194,304 = 2 22 个未使用的字符。然而,2,097,152 = 2 21是 AMD64 (x86-64) 上巨大页面的大小,并且是基本上所有架构上原生页面大小的 2 的幂倍数。它也足够大,可以在基本上所有这样做的架构上从所谓的 sbrk() 分配切换到内存映射。这意味着如此巨大的分配使用堆的单独部分,而未使用的部分通常只是虚拟内存,不一定由任何 RAM 支持,直到被访问。因此,在大多数体系结构上,该策略对于非常短的字节数组和非常长的字节数组都非常有效。
当然,如果您知道(或测量!)典型工作负载中字节数组的典型大小,您可以为此优化增长策略,并获得更好的结果。
最后,它使用memmove()
代替memcpy()
,以防万一有人希望重复同一字节数组的一部分:memcpy()
仅在源区域和目标区域不重叠时才有效;memmove()
即使在这种情况下也有效。
当使用更高级的数据结构(如哈希表)时,上述结构的变体通常很有用。(也就是说,在您有很多空字节数组的情况下,这会更好。)
数据不是指向数据的指针,而是结构本身的一部分,作为 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()
/ realloc()
do 一样。但是,由于所需的新长度为零,因此通过释放旧字节数组(如果有)来节省内存,并返回NULL
. 但由于这不是错误,我们设置errno
为零,以便调用者更容易检查是否发生错误。(如果函数返回NULL
,请检查errno
。如果errno
为非零,则发生错误;您可以使用它strerror(errno)
来获取描述性错误消息。)
您可能还注意到sizeof barray->data[0]
, 即使barray
是 NULL 也是如此。这没关系,因为sizeof
它不是一个函数,而是一个运算符:它根本不访问右侧,它只计算右侧所指事物的大小。(仅当正确的大小是类型时才需要使用括号。)这种形式很好,因为它允许程序员更改data
成员的类型,而无需更改任何其他代码。
要将数据追加到这样的字节数组,我们可能希望能够指定我们是否期望进一步追加到同一个数组,或者这是否可能是最终追加,以便只需要确切需要的内存量。为简单起见,我将仅在此处实现确切大小的版本。请注意,此函数返回一个指向(修改后的)字节数组的指针:
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
,因为我们改变barray
了函数中指向的位置。如果第四个参数 ,final
不为零,则生成的字节数组正是所需的大小;否则应用增长策略。
推荐阅读
- react-native - React Native 屏幕应该根据前一个屏幕重新加载
- selenium - 在 SPA 网站中抓取(Selenium Chrome)
- adobe-documentgeneration - Adobe Document Generation 中同一行的条件逻辑无法识别模板标签
- reactjs - 如何使用 React 和 Typescript 创建无序列表
- lua - Roblox Studio Lua 问题“Workspace.Ultimate.Script:12:尝试比较数字 < 字符串”-
- wordpress - 我无法重置 apache。(Linux)
- java - 如何为从具有泛型的抽象类继承的类创建构造函数?
- visual-studio-code - 带有当前激活的 virtualenv 的 vscode 命令行
- ruby - 在 dragon ruby 工具包中需要一个文件
- database - 如何向 dolphindb 中的现有分区表添加新列?