【问题标题】:Array entries being overwriten in C在 C 中被覆盖的数组条目
【发布时间】:2019-11-08 17:38:57
【问题描述】:

我想将整数数组转换为字符串数组。例如,如果arr[] = {1, 2, 3, 4, 5},我想以arr2[] = {"1", "2", "3", "4", "5"} 结尾。此函数工作正常,直到它退出 for 循环,其中所有数组条目都被最后一个条目的值覆盖。知道为什么会发生这种情况吗?

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

#define SIZE 5
int main(){
    int nums[] = {1, 2, 3, 4, 5};
    char **strings = (char **)malloc(SIZE * sizeof(char*));

    for(int i = 0; i < SIZE; i++){
        char temp[20];
        sprintf(temp, "%d", nums[i]);
        strings[i] = temp;
        //prints correct values here
        printf("%s\n", strings[i]);
    }

    for(int i = 0; i < SIZE; i++){
        //prints wrong values here
        printf("%s\n", strings[i]);
    }

    free(strings);
    return 0;
}

【问题讨论】:

  • temp 是循环内的局部变量。每次迭代都会超出范围,并且它的生命周期将结束。你所有的指针都指向这个数组,所以在循环之后你的所有指针都将无效。以任何方式使用它都会导致未定义的行为
  • 您的temp 只进行了一次迭代。
  • 更不用说你在一个只包含四个元素的数组上迭代了五次。
  • @Someprogrammerdude 是的,这是我的错误,数组应该是 {1, 2, 3, 4, 5}
  • 那么请编辑您的问题以解决它。带有不相关错误和问题的minimal reproducible example 会分散您所询问的实际问题的注意力。

标签: c arrays string pointers


【解决方案1】:

问题是strings[i] = temp;。这会将temp 分配给strings[i],但随后temp 是作用域为循环块的局部变量,并且在块终止后无效。

您需要为每个字符串使用malloc 内存来保存复制的值(完成后还需要free)。 tmp 也是不必要的,因为我们可以将sprintf 直接放入指针中。

SIZE = 5 但你的数组只有 4 个成员,所以我们有一个越界访问。我更愿意将此范围限定为它所代表的数据,而不是将其设为全局常量。我还假设该数组将处理任意数据,因为按原样,与在循环中使用 i + 1 相比,它没有任何优势。

malloc(12) 有足够的空间来保存 32 位 int 字符串(sizeof char 始终为 1,我们需要空间来存放 '-''\0' 字符)。正如this comment 中所指出的,您可以使用sizeof(int) * CHAR_BIT / 3 + 2 来计算CHAR_BITlimits.h 标头中定义的缓冲区的正确大小。

顺便说一句,no need 可以转换为malloc,最好使用sizeof(*strings),以防在重构期间指针类型发生变化。

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

int main() {
    int nums[] = {1, 2, 3, 4};
    int nums_size = 4;
    char **strings = malloc(nums_size * sizeof(*strings));

    for (int i = 0; i < nums_size; i++) {
        strings[i] = malloc(12);
        sprintf(strings[i], "%d", nums[i]);
    }

    for (int i = 0; i < nums_size; i++) { 
        printf("%s\n", strings[i]);
        free(strings[i]);
    }

    free(strings);
    return 0;
}

【讨论】:

  • 感谢您的帮助和建议,您的解决方案最适合我正在尝试做的事情!
【解决方案2】:

总是让编译器为你做计数。

首先,我在下面定义了一个宏COUNTOF,它产生数组对象中items的数量(而不是bytes的数量);应该在任何地方使用。

其次,strdup() 库函数复制一个字符串缓冲区——它计算长度,分配正确的字节数,然后将字符串复制到其中。比自己滚动要容易得多。

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

#define COUNTOF(x)   ( sizeof(x) / sizeof((x)[0]) )

int main(){
    int nums[] = {1, 2, 3, 4};
    char **strings = malloc(COUNTOF(nums) * sizeof(*strings));

    for(int i = 0; i < COUNTOF(nums); i++){
        char temp[40];  // bigger than any plausible int
        sprintf(temp, "%d", nums[i]);
        strings[i] = strdup(temp);   // HERE
        printf("%s\n", strings[i]);
    }

    for(int i = 0; i < COUNTOF(nums); i++){
        //now prints correct value here :-)
        printf("%s\n", strings[i]);
    }

    return 0;
}

【讨论】:

  • 并且@ggorlen 正确地提醒您在完成后释放内存。
  • 旁白:char temp[20]; 适用于 32 位数学。另一个是char temp[sizeof(int)*CHAR_BIT/3 +2]; 或其他类似的自缩放缓冲区大小。
  • 好点;它是 21 字节来支持 64 位的东西,所以把它变成 40 字节并没有什么坏处。
  • 当我正在寻找快速编码并且不想计算缓冲区需求时,我会考虑支持最坏的情况,例如 int128,然后将其加倍。
  • 请注意strdup 不是标准C。
【解决方案3】:

实际上没有必要在运行时执行此操作。如果您可能会遇到一些“X 宏”技巧,您可以在编译时执行此操作。

#define INIT_LIST \
   X(1) \
   X(2) \
   X(3) \
   X(4) \

#define STR(n) #n

#include <stdio.h>

int main (void)
{
  #define X(n) n,
    int nums[] = {INIT_LIST};
  #undef X

  #define X(n) STR(n),
    const char* str[] = {INIT_LIST};
  #undef X

  for(size_t i=0; i<sizeof(nums)/sizeof(*nums); i++)
  {
    printf("%d %s\n", nums[i], str[i]);
  }
}

其中INIT_LIST 必须将所有初始化程序都包含为整数。预处理后,上面展开成这样:

#include <stdio.h>

int main (void)
{
  int nums[] = {1,2,3,4};
  const char* str[] = {"1", "2", "3", "4"};

  for(size_t i=0; i<sizeof(nums)/sizeof(*nums); i++)
  {
    printf("%d %s\n", nums[i], str[i]);
  }
}

在性能方面,这自然比任何使用堆分配的解决方案都要快得多。缺点是 X 宏可能有点难以阅读。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-21
    • 1970-01-01
    • 1970-01-01
    • 2012-05-09
    • 2018-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多