2014-12-10 66 views
1

我目前正在阅读21世纪C,并且玩弄了一些人为的示例代码。在C99中收集没有浪费malloc的字符串

我试图解决这个问题不重复malloc()realloc()荷兰国际集团,缓冲区中的问题,完整的代码here,但我下面联的重要组成部分:

功能_sgr_color(int modes[])应该叫一样,既是等价的,第二个是宏包装复合文字:

_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 
    * \0 
    * 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 "\0"; 
    } 
    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 \0 
    * 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 `\0' characters. 
    * > Otherwise, dst is not terminated. 
    * 
    */ 
    strncpy(dest, buffer, len); 
    return dest; 
} 

这里的代码工作,一个示例程序输出正确的b ytes按照正确的顺序和颜色代码工作,如标注但是存在的问题:

  1. 采用asprintf()破坏我的理由为希望不要重复调用malloc()

我很努力地看到如何简化这段代码,如果有的话,以及如何可能会危及我不想重复分配内存的愿望。

+0

避免重复分配内存的唯一方法是为整个作业前面分配足够的。如果你不能这样做,那么这是一个如何组织这些分配的问题。 – 2014-12-10 22:19:08

+0

你可以用你的专业知识,以组成一个答案,我可以接受,也许是分配在堆上合理大小的缓冲,迭代的事情,不知何故检测(用'strncpy'?)字符串的结束,mallocing和复制只是那点?另外我怎样才能将整数转换为char *而不用'asprintf'调用? – 2014-12-10 22:32:50

+0

注意:'sizeof(enum color)'不是估计所需缓冲区大小的正确方法。它给出了'enum color'的内部表示的大小,而不是* formatted *表示的大小。你是 – 2014-12-10 22:52:10

回答

1

看起来您可以根据给定模式的数量和其允许值的已知边界(加上所涉及的常量字符串的已知长度)来计算所需的最大空间。在这种情况下,可能最少的动态内存分配将通过

  • 执行只是一个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; 
} 
+0

感谢约翰,伟大的意见,这是我错过了,没有使用'asprintf',我怎样才能打印字符串的两种长度,并将其写入到合适的位置,我想我可以做'炭TMP = sprintf的( “%d”,模式[I])'和'strlen的(TMP)',并使用这些缓冲液和偏移附加到正确的位置中,最后一个字符串(前'的realloc( )更小的尺寸 – 2014-12-10 23:03:23

+0

如果你使用普通的'sprintf()'直接写入缓冲区,那么返回写入的字符数。我提供了一个我认为非常紧密的示例实现。 – 2014-12-10 23:21:07

+0

请注意,示例代码不执行错误检查,除了末尾的(可选)'realloc()'外。可能至少应该检查'malloc()',因为如果'malloc()'失败,代码将崩溃并烧毁。几个'sprintf()'不可能失败,但是如果你想运行最大的安全性,那么你也会检查它们的返回值。 – 2014-12-11 15:09:23