我目前正在阅读21st Century C,并玩弄一些人为的示例代码。
我试图解决的问题不是反复malloc()
并realloc()
缓冲区,完整的代码在这里,但我在下面内联了重要部分:
函数_sgr_color(int modes[])
应该调用 like,两者都是等价的,第二个是包装复合文字的宏:
_sgr_color((int[]){31,45,0}); // literal function
sgv_color(31, 45); // va macro wrapper
这些都应该返回类似 x1b[;31;45m
的内容。
然而,幻数在原始代码中被定义为常量(typedef enum SGR_COLOR { SGR_COLOUR_BLACK = 31}
等(。
在函数_sgr_color(int modes[])
里面,我知道我需要分配一个缓冲区,并返回它,没问题,但我不知道要做多长时间,直到我走modes[]
:
我已经内联注释了代码:
/* Static, to make callers use the macro */
static char *_sgr_color(int modes[]) {
/* We increment the writeOffset to move the "start" pointer for the memcpy */
int writeOffset = 0;
/* Initial length, CSI_START and CSI_END are `x1b[' and `m' respectively */
int len = strlen(CSI_START CSI_END);
/* Loop over modes[] looking for a 0, then break, count the number of entries
* this is +1 to account for the ; that we inject.
*/
for (int i = 0; modes[i] > 0; i++) {
len += sizeof(enum SGR_COLOR) + 1;
}
/* Local buffer, unsafe for return but the length at least is right (no +1 for
*
* because we are in control of reading it, and we'll allocate the +1 for the
* buffer which we return
*/
char buffer[len];
/* Copy CSI_START into our buffer at position 0 */
memcpy(buffer, CSI_START, strlen(CSI_START));
/* Increment writeOffset by strlen(CSI_START) */
writeOffset += strlen(CSI_START);
/* Loop again over modes[], inefficient to walk it twice,
* but preferable to extending a buffer in the first loop with
* realloc().
*/
for (int i = 0; modes[i] > 0; i++) {
/* Copy the ; separator into the buffer and increment writeOffset by
* sizeof(char) */
memcpy(buffer + writeOffset, ";", sizeof(char));
writeOffset += sizeof(char);
/* Write the mode number (int) to the buffer, and increment the writeOffset
* by the appropriate amount
*/
char *modeistr;
if (asprintf(&modeistr, "%d", modes[i]) < 0) {
return " ";
}
memcpy(buffer + writeOffset, modeistr, sizeof(enum SGR_COLOR));
writeOffset += strlen(modeistr);
free(modeistr);
}
/* Copy the CSI_END into the buffer, no need to touch writeOffset */
memcpy(buffer + writeOffset, CSI_END, strlen(CSI_END));
char *dest = malloc(len + 1);
/* Copy the buffer into the return buffer, strncopy will fill the +1 with
* as per the documentation:
*
* > The stpncpy() and strncpy() functions copy at most n characters
* > from src into dst. If src is less than n characters long, the
* > remainder of dst is filled with ` ' characters.
* > Otherwise, dst is not terminated.
*
*/
strncpy(dest, buffer, len);
return dest;
}
在这里,代码有效,示例程序以正确的顺序输出正确的字节,并且颜色代码有效,但是存在标记的问题:
- 使用
asprintf()
破坏了我不想反复打电话给malloc()
的理由。
我正在努力了解如何简化此代码(如果有的话(,以及这可能会如何损害我不重复分配内存的愿望。
看起来您可以根据给定的模式数量及其允许值的已知边界(加上所涉及的常量字符串的已知长度(计算所需的最大空间。 在这种情况下,可能最少的动态内存分配将通过
- 只需执行一个
malloc()
即可获得足够大的缓冲区来容纳所有内容,而不管模式的实际值如何, - 直接写入该缓冲区(无
asprintf()
(, - 并最终返回它(不需要复制出本地暂存缓冲区(。
如果需要,您可以跟踪实际写入的数据量,并在最后realloc()
将缓冲区缩小到实际使用的空间(这应该是便宜和可靠的,因为您将减少分配(。 如果内存充足,则可以跳过realloc()
- 输出缓冲区将占用比它需要的更多的内存,但是当缓冲区被释放时,它将恢复。
这将比预先计算每个模式缓冲区中需要多少空间更容易、更可靠,甚至可能更快,这是最小化动态分配的另一种选择。
例如:
/* The maximum number of decimal digits in a valid mode */
#define MODE_MAX_DIGITS 6
static char *_sgr_color(int modes[]) {
char *buffer;
char *buf_tail;
char *temp;
/* Space required for the start and end sequences, plus a string terminator
* (-1 instead of +1 because the two sizeofs each include space for one
* terminator)
*/
int len = sizeof(CSI_START) + sizeof(CSI_END) - 1;
/* Increase the required length to provide enough space for all the modes and
* their semicolon separators.
*/
for (int i = 0; modes[i] > 0; i++) {
len += MODE_MAX_DIGITS + 1;
}
/* Allocate a buffer big enough to hold the entire result, no matter
* what the actual mode values are
*/
buffer = malloc(len);
buf_tail = buffer;
/* Copy CSI_START into our buffer at the current position (the beginning),
* and advance the tail pointer to the next available position
*/
buf_tail += sprintf(buf_tail, "%s", CSI_START);
/* Loop again over modes[]. It's more efficient to walk it twice than to
* repeatedly extend the buffer as would be required to walk it only once.
*/
for (int i = 0; modes[i] > 0; i++) {
/* Write the ; separator and mode into the buffer; track the buffer tail */
buf_tail += sprintf(buf_tail, ";%d", modes[i]);
}
/* Copy the CSI_END into the buffer, and update the buffer tail */
buf_tail += sprintf(buf_tail, "%s", CSI_END);
/* shrink the buffer to the space actually used (optional) */
temp = realloc(buffer, 1 + buf_tail - buffer);
/* realloc() should not fail in this case, but if it does then temp
* will be NULL and buffer will still be valid. Else temp PROBABLY
* is equal to buffer, but that's not guaranteed.
*/
return temp ? temp : buffer;
}