使用C,将动态分配的int数组尽可能干净地转换为逗号分隔的字符串

时间:2009-11-17 00:26:33

标签: c string

我在C语言方面的经验比在高级语言方面要差得多。在Cisco,我们使用C,有时我会遇到一些在Java或Python中很容易做到的事情,但在C中很难做到。现在就是其中之一。

我有一个动态分配的无符号整数数组,我需要转换为以逗号分隔的字符串进行日志记录。虽然整数不太可能非常大,但它们在概念上可能是0到4,294,967,295在Python中,这只是一条短线。

my_str = ','.join(my_list)

人们在C中做到这一点有多优雅?我提出了一种方法,但这很糟糕。如果有人知道这样做的好方法,请赐教。

14 个答案:

答案 0 :(得分:9)

代码现已在gcc下进行测试和构建。

与其他答案相反,不强制要求C99。

这里真正的问题是不知道你需要的字符串的长度。获取一个数字就像使用sprintf("%u", *num)numint的数组size_t join_integers(const unsigned int *num, size_t num_len, char *buf, size_t buf_len) { size_t i; unsigned int written = 0; for(i = 0; i < num_len; i++) { written += snprintf(buf + written, buf_len - written, (i != 0 ? ", %u" : "%u"), *(num + i)); if(written == buf_len) break; } return written; } 一样简单,但是您需要多少空间?为了避免超出缓冲区,你必须跟踪很多整数。

snprintf

请注意,我会跟踪我使用了多少缓冲区并使用snprintf,因此我不会超出结束范围。 \0buf + written,但由于我使用的是\0,我将从之前snprintf的{​​{1}}处开始。

使用中:

int main() {
    size_t foo;
    char buf[512];

    unsigned int numbers[] = { 10, 20, 30, 40, 1024 };

    foo = join_integers(numbers, 5, buf, 512);
    printf("returned %u\n", foo);
    printf("numbers: %s\n", buf);
}

输出:

returned 20
numbers: 10, 20, 30, 40, 1024

强制限制进入,而不是超越:

char buf[15];    
foo = join_integers(numbers, 5, buf, 14);
buf[14] = '\0';

输出,预计:

returned 14
numbers: 10, 20, 30, 4

答案 1 :(得分:2)

这个怎么样?

char *join_int_list(const unsigned int *list, size_t n_items)
{
     enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 };
     char *space = malloc(SIZEOF_INT_AS_STR * n_items);
     if (space != 0)
     {
         size_t i;
         char *pad = "";
         char *dst = space;
         char *end = space + SIZEOF_INT_AS_STR * n_items;
         for (i = 0; i < n_items; i++)
         {
              snprintf(dst, end - dst, "%s%u", pad, list[i]);
              pad = ",";
              dst += strlen(dst);
         }
         space = realloc(space, dst - space + 1);
     }
     return(space);
}

调用者有责任释放返回的指针 - 并在使用它之前检查它是否为null。如果分配的金额太大而无法使其值得,那么'realloc()'会释放额外的空间。这段代码巧妙地假设这些值确实是32位无符号整数;如果它们可以更大,那么枚举需要适当的调整。

经过测试的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *join_int_list(const unsigned int *list, size_t n_items)
{
    enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 };
    char *space = malloc(SIZEOF_INT_AS_STR * n_items);
    if (space != 0)
    {
        size_t i;
        char *pad = "";
        char *dst = space;
        char *end = space + SIZEOF_INT_AS_STR * n_items;
        for (i = 0; i < n_items; i++)
        {
            snprintf(dst, end - dst, "%s%u", pad, list[i]);
            pad = ",";
            dst += strlen(dst);
        }
        space = realloc(space, dst - space + 1);
    }
    return(space);
}

int main(void)
{
    static unsigned int array[]= { 1, 2, 3, 49, 4294967295U, 0, 332233 };
    char *str = join_int_list(array, sizeof(array)/sizeof(array[0]));
    printf("join: %s\n", str);
    free(str);
    return(0);
}

用valgrind检查 - 似乎没问题。


讨论将INT_MAXUINT_MAX转换为字符串:

  

您可以使用sizeof(“,”STRINGIZE(INT_MAX))而不是硬编码。 stringize宏是一个常见的cpp工具,可以定义为#define STRINGIZE_(v)#v和#define STRINGIZE(v)STRINGIZE_(v)。 - R. Pate

     

@R Pate:好主意 - 是的,你可以非常有效地做到这一点。实际上,有两个有趣的想法:使用字符串连接和sizeof()(为清晰起见需要括号 - 但字符串连接发生得足够早以至于编译器不担心)以及在{{1上使用字符串化操作}}。 - 乔纳森莱弗勒

     

INT_MAX上使用字符串化操作不是一个好主意 - 它只需要是一个“常量表达式”,不一定是数字序列。它可以被定义为((1 <&lt; 32)-1,或甚至像__int_max那样令人讨厌,只要编译器允许您在任何可以使用常量表达式的地方使用它。 - 咖啡馆

@caf是对的。请考虑以下代码:

INT_MAX

这甚至不能在带有GCC 4.0.1的MacOS X 10.5.8上编译 - 因为未定义标识符#include <limits.h> #include <stdio.h> #undef INT_MAX #define INT_MAX (INT_MIN-1 - 100 + 100) #define QUOTER(x) #x #define STRINGIZER(x) QUOTER(x) enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 }; enum { SIZEOF_INT_AS_STR_1 = sizeof(STRINGIZER(INT_MAX) ",")-1 }; int main(void) { printf("size = %d = %d\n", SIZEOF_INT_AS_STR, SIZEOF_INT_AS_STR_1); printf("INT_MAX = %d\n", INT_MAX); printf("UINT_MAX = %u\n", UINT_MAX); return(0); } 。未打印INT_MAXINT_MAX的代码的初步版本有效;它表明UINT_MAX的值为31 - 所以@caf是正确的。添加对SIZEOF_INT_AS_STR_1INT_MAX的值的双重检查然后无法编译,这让我感到惊讶。查看UINT_MAX的输出可以了解原因:

gcc -E

正如预测的那样,enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 }; enum { SIZEOF_INT_AS_STR_1 = sizeof("((-INT_MAX - 1)-1 - 100 + 100)" ",")-1 }; int main(void) { printf("size = %d = %d\n", SIZEOF_INT_AS_STR, SIZEOF_INT_AS_STR_1); printf("INT_MAX = %d\n", ((-INT_MAX - 1)-1 - 100 + 100)); printf("UINT_MAX = %u\n", (((-INT_MAX - 1)-1 - 100 + 100) * 2U + 1U)); return(0); } 的字符串根本不是数字字符串。预处理器可以评估表达式(尽可能多),但不必生成数字字符串。

SIZEOF_IN_AS_STR_1的扩展原来是INT_MAX,而INT_MIN又是INT_MIN的定义,所以当重写时INT_MAX {1}}宏被评估,C预处理器操作规则阻止了“递归扩展”,并且INT_MAX出现在预处理输出中 - 引起了所有人的混淆。

因此,有多种原因导致表面上有吸引力的想法变成一个坏主意。

答案 2 :(得分:2)

您实际上可以使用像Glib这样的库,其中包含

等功能
  

gchar * g_strjoin(const gchar * separator,                                                            ...);

     

将多个字符串连接在一起形成一个长字符串,并在每个字符串之间插入可选的分隔符。应使用g_free()释放返回的字符串。

(您仍需要使用g_snprintf(),可能需要g_printf_string_upper_bound()以确保空间)

答案 3 :(得分:1)

char buf [11 * sizeof (my_list)];
for (int n = 0, int j = 0;  j < sizeof (my_list) / sizeof (my_list [0]);  ++j)
    n += sprintf (&buf [n], "%s%u",   (j > 0) ? "," : "",  my_list [j]);

答案 4 :(得分:1)

unsigned *a; /* your input a[N] */
unsigned i,N;
char *b,*m;
b=m=malloc(1+N*11); /* for each of N numbers: 10 digits plus comma (plus end of string) */
for (i=0;i<N;++i)
  b+=sprintf(b,"%u,",a[i]);
if (N>0) b[-1]=0; /* delete last trailing comma */
/* now use m */
free(m);

漂亮,对吗? :)

答案 5 :(得分:1)

#include <stdio.h>
#include <stdlib.h>

/* My approach is to count the length of the string required. And do a single alloc.
     Sure you can allocate more, but I don't know for how long this data will be retained.
*/ 

#define LEN(a) (sizeof a / sizeof *a)

int main(void) {

    unsigned a[] = {1, 23, 45, 523, 544};
    int i, str_len=0, t_written=0;
    char tmp[11]; /* enough to fit the biggest unsigned int */

    for(i = 0; i < LEN(a); i++) 
        str_len += sprintf(tmp, "%d", a[i]);

    /* total: we need LEN(a) - 1 more for the ',' and + 1 for '\0' */
    str_len += LEN(a);
    char *str = malloc(str_len); 
    if (!str) 
        return 0;

    if (LEN(a) > 1) {
        t_written += sprintf(str+t_written, "%d", a[0]);
        for(i = 1; i < LEN(a); i++)
            t_written += sprintf(str+t_written, ",%d", a[i]);
    } else if (LEN(a) == 1) 
        t_written += sprintf(str+t_written, "%d", a[0]);

    printf("%s\n", str);

    free(str);
    return 0;
}

答案 6 :(得分:1)

你们和你的不必要的特殊情况要处理尾随的逗号......只要破坏最后一个逗号然后每次循环运行时进行条件检查便宜。

:)

#include <stdio.h>

char* toStr(int arr[], unsigned int arrSize, char buff[])
{
    if (arr && arrSize && buff)
    {
        int* currInt = arr;
        char* currStr = buff;
        while (currInt < (arr + arrSize))
        {
            currStr += sprintf(currStr, "%d,", *currInt++);
        }
        *--currStr = '\0';
    }
    return buff;
}

int main()
{
    int arr[] = {1234, 421, -125, 15251, 15251, 52};
    char buff[1000];

    printf("Arr is:%s\n", toStr(arr, 6, buff));    
}

假设buff足够大,将其分配为(最大int + 2的长度)* arrSize)。灵感来自我的memcpy:)

修改 我意识到我之前有一个brainfart,可能只是增加了sprintf的返回值,而不是存储temp。显然,其他答案也是如此,编辑我的答案以删除2条不必要的行。

<强> EDIT2 看起来像争吵打败了我!他的回答与我的回答非常相似,并且早先提交过。我谦卑地建议给他+ 1。

答案 7 :(得分:1)

你们是否通过线路获得报酬? : - )


f()声明为char *参数用于原型设计,只需更改char -> int。我将这个问题解释为需要一个字符串作为输出,而不仅仅是代码来写入文件。

#define PRINT(s, l, x, i) snprintf((s), (l), "%s%d", (i) ? ",":"", (x)[i]);

char *f(size_t len, char *x) {
  size_t  i, j = 0, k;

  for(i = 0; i < len; ++i)
      j += PRINT(NULL, 0, x, i);
  char *result = malloc(k = ++j);
  for(*result = i = j = 0; i < len; ++i)
      j += PRINT(result + j, k - j, x, i);
  return result;
}

这是一个测试框架:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// put f() here

int main(int ac, char **av) {
    for(int i = 1; i < ac; ++i) { 
        printf("%s\n", f(strlen(av[i]), av[i]));
    }
    return 0;
}

答案 8 :(得分:0)

假设当你提到“for logging”时你的意思是写入日志文件,那么你的解决方案可能看起来像这样(伪编码):

for (int x in array) {
    fprintf(log, "%d", x);
    if (! last element)
        fputc(log, ',');
}

答案 9 :(得分:0)

就个人而言,为了简单起见,也可能是速度,我会使用一个大缓冲区,每个元素的大小为“4,294,967,295”和“,”的数组空间。虽然在创建列表时它不是空间效率!

然后我将冲刺进入那里,并将“,”附加到所有元素

最后,我将指针重新分配给没有超出要求的空间。 (size = strlen)

sprintf:成功时,返回写入的字符总数。此计数不包括自动附加在字符串末尾的附加空字符。

这就是你如何跟踪字符串中strcpy的位置。 :)

希望有所帮助! :)

如果您只想将它​​们打印出来,请参阅其他回复。 (for-loop和printf)

答案 10 :(得分:0)

不幸的是总会有三种情况:

  • 空列表(无逗号,无项目)
  • 一个项目(没有逗号,一个项目)
  • 两个或更多项目(n-1个逗号,n个项目)

join方法为您隐藏了这种复杂性,这就是它如此美妙的原因。

在C中,我会这样做:

for (i = 0; i < len; i++)
{
    if (i > 0)   /* You do need this separate check, unfortunately. */
        output(",");
    output(item[i]);
}

然而,output在哪里附加到字符串。它可以像预先分配的缓冲区上的strcat一样简单,也可以像某个流的printf一样简单(就像我今天在Creating a FILE * stream that results in a string中了解到的内存流: - )。

如果您对所有i&gt; = 1的每次检查感到恼火,您可以这样做:

if (i > 0)
{
    output(item[0]);
    for (i = 1; i < len; i++)
    {
        output(",");
        output(item[i]);
    }
}

答案 11 :(得分:0)

如果你想要一个文件,Steven Schlansker的答案很好。

但是,如果你想把它放在一个字符串中,事情会变得更复杂。您可以使用sprintf,但需要注意不要在字符串中耗尽空间。如果你有一个C99兼容的snprintf(Linux,BSD,而不是Windows),下面的(未经测试,未编译的)代码应该可以工作:

char *buf = malloc(1024); /* start with 1024 chars */
size_t len = 1024;
int pos = 0;
int rv;
int i;
for (i = 0; i < n; i++) {
    rv = snprintf(buf+pos, len - pos, "%s%d", i = 0 ? "" : ",", my_list[i]);
    if (rv < len - pos) {
        /* it fit */
        pos += rv;
    } else {
        len *= 2;
        buf = realloc(buf, len);
        if (!buf) abort();
        i--; /* decrement i to repeat the last iteration of the loop */
    }
}
return buf;

然后,来电者必须免费buf

答案 12 :(得分:0)

void join(int arr[], int len, char* sep, char* result){
    if(len==0){
        *result='\0';
    } else {
        itoa(arr[0],result,10);
        if(len > 1){
            strcat(result,sep);
            join(arr+1,len-1,sep,result+strlen(result));
        }
    }
}

答案 13 :(得分:0)

这是一个线性解决方案,它为调用者分配一个呈指数增长的缓冲区(减少对 realloc 的调用,如果重要的话)。包含测试脚本。

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void ensure(bool pred, char *msg, char *file, int line) {
    if (!pred) {
        fprintf(stderr, "%s:%d: %s", file, line, msg);
        exit(1);
    }
}

char *arr_to_s(int len, int *arr, char *sep) {
    size_t sep_len = strlen(sep);
    int result_capacity = 16 + sep_len;
    int result_len = 0;
    char *result = malloc(result_capacity);
    ensure(result, "malloc", __FILE__, __LINE__);
    result[0] = '\0';
  
    for (int i = 0; i < len; i++) {
        char num[16+sep_len];
        int previous_len = result_len;
        result_len += sprintf(num, i < len - 1 ? "%d%s" : "%d", arr[i], sep);
      
        if (result_len >= result_capacity) {
            result_capacity <<= 1;
            result = realloc(result, result_capacity);
            ensure(result, "realloc", __FILE__, __LINE__);
        }
      
        strcat(result + previous_len, num);
    }
  
    return result;
}

void run_basic_tests(void) {
    int tests[][4] = {
        {0},
        {0, 1},
        {0, 1, 2},
        {0, 42, 2147483647, -2147483648},
    };
    
    for (int i = 0; i < 4; i++) {
        char *s = arr_to_s(i + 1, tests[i], ", ");
        printf("[%s]\n", s);
        free(s);
    }
}

void run_intensive_tests(int n) {
    srand(42);

    for (int i = 0; i < n; i++) {
        int len = rand() % 2000;
        int test[len];

        printf("[");

        for (int j = 0; j < len; j++) {
            test[j] = rand() % 2000000000 - 1000000000;
            printf(j < len - 1 ? "%d," : "%d", test[j]);
        }

        puts("]");
        
        char *s = arr_to_s(len, test, ",");
        printf("[%s]\n", s);
        free(s);
    }
}

int main(void) {
    //run_basic_tests();
    run_intensive_tests(10000);
    return 0;
}

测试运行器:

#!/usr/bin/env bash

gcc -std=c99 -pedantic -Wall \
    -Wno-missing-braces -Wextra -Wno-missing-field-initializers -Wformat=2 \
    -Wswitch-default -Wswitch-enum -Wcast-align -Wpointer-arith \
    -Wbad-function-cast -Wstrict-overflow=5 -Wstrict-prototypes -Winline \
    -Wundef -Wnested-externs -Wcast-qual -Wshadow -Wunreachable-code \
    -Wlogical-op -Wfloat-equal -Wstrict-aliasing=2 -Wredundant-decls \
    -Wold-style-definition -Werror \
    -ggdb3 \
    -O0 \
    -fno-omit-frame-pointer -ffloat-store -fno-common -fstrict-aliasing \
    -lm \
    -o arr_to_s.out \
    arr_to_s.c

./arr_to_s.out > arr_to_s_test_out.txt
cat arr_to_s_test_out.txt | awk 'NR % 2 == 1' > arr_to_s_test_expected.txt
cat arr_to_s_test_out.txt | awk 'NR % 2 == 0' > arr_to_s_test_actual.txt
diff arr_to_s_test_expected.txt arr_to_s_test_actual.txt

瓦尔格林:

==573== HEAP SUMMARY:
==573==     in use at exit: 0 bytes in 0 blocks
==573==   total heap usage: 103,340 allocs, 103,340 frees, 308,215,716 bytes allocated
==573==
==573== All heap blocks were freed -- no leaks are possible