【问题标题】:Array of structs inside a loop循环内的结构数组
【发布时间】:2017-01-09 21:49:03
【问题描述】:

我正在学习 C 并且正在玩结构,但我发现了一些我无法解释的行为,我想知道它为什么会发生。

这是我的代码:

struct my_struct{
  char *name;
};

int main()
{

  struct my_struct arr[3];
  int i = 0;
  char str[10];

  while (i<3)
  {
    fgets(str, 10, stdin);
    arr[i].name = str;
    printf("Array number %d: %s", i, arr[i].name); 
    i++;
  }

  printf("1 - %s\n2 - %s\n3 - %s", arr[0].name, arr[1].name, arr[2].name);

  return 0;
}

我的意见:

test1
test2
test3

预期输出:

Array number 0: test1

Array number 1: test2

Array number 2: test3
1 - test1

2 - test2

3 - test3

结果输出:

Array number 0: test1

Array number 1: test2

Array number 2: test3
1 - test3

2 - test3

3 - test3

结果输出:

问题是,只要 while 循环继续运行,它似乎就可以了;但是,当它退出时,它似乎将数组中结构的每个“名称”值设置为最后一个。

如果,一旦退出循环并在最后一个 printf() 之前,我手动设置了数组中最后一个结构的名称,这是唯一更新的结构,但之前的结构的名称仍然设置为最后一个进入循环内。

我想我错过了一些关于内存管理的信息,比如在再次调用 fgets() 之前刷新缓冲区或某事,但无法弄清楚发生了什么。有人知道这是怎么回事吗?

【问题讨论】:

    标签: c arrays loops struct


    【解决方案1】:

    这是你所期望的,这样想,你有char str[10],这是你的字符串存储的内存。当您使用arr[i].name = str 设置每个数组名称时,您将char * name 指向此内存。所以这就是你的 for 循环正在做的事情:

    0. str = [];         arr[0].name = NULL; arr[1].name = NULL; arr[1].name = NULL;
    1. str = [string1];  arr[0].name = str;  arr[1].name = NULL; arr[1].name = NULL;
    2. str = [string2];  arr[0].name = str;  arr[1].name = str;  arr[1].name = NULL;
    3. str = [string3];  arr[0].name = str;  arr[1].name = str;  arr[1].name = str;
    

    所以到循环结束时,所有arr.name 指针都指向字符串,并且您每次都编辑了字符串。如果您希望各个 arr 元素存储自己的字符串,那么您最好这样做:

    struct my_struct{
      char name[10]; // Note how each instance of `my_struct` now stores its own string.
    };
    
    int main() {
      struct my_struct arr[3];
      int i = 0;
    
      while (i<3) {
        fgets(arr[i].name, 10, stdin);
        printf("Array number %d: %s", i, arr[i].name); 
        i++;
      }
    
      printf("1 - %s\n2 - %s\n3 - %s", arr[0].name, arr[1].name, arr[2].name);
    
      return 0;
    }
    

    Live example

    最后一点,您应该避免使用fgets(see here)。更喜欢getline

    【讨论】:

    • @Nico 没问题,为了让这个问题成为一个很好的终身问题,您可以添加输入字符串和一段缩进代码,显示您的预期输出和实际得到的结果。这样对未来的读者来说就更清楚了。
    【解决方案2】:

    你不能像arr[i].name = str那样复制一个C字符串。

    您所做的是将每个name 指针设置为相同的地址,由str 表示。因此,当您调用printf 时,每个name 都指向同一个字符串str,而printf 只需打印三遍str

    如果要复制字符串,请使用strcpy。此外,您需要为 name 变量分配内存。

    【讨论】:

      【解决方案3】:

      你必须为每个结构的 char *name 分配内存:

      while (i<3)
        {
          fgets(str, 10, stdin);
          arr[i].name=(char *)malloc(10*sizeof(char));
          strcpy(arr[i].name,str);
      
          printf("Array number %d: %s", i, arr[i].name); 
          i++;
        }
      

      请注意,通过这种方式,当您使用完数组 [i] 后,您应该为每个 i

      【讨论】:

      • 如果你给出这个例子,你还应该提到,在作用域的末尾应该释放内存以避免内存泄漏。
      • @Ben,谢谢!!我认为这样说是多余的,但我的答案应该更具体,我重新编辑了答案。
      【解决方案4】:

      因为这是C,所以你需要自己管理所有的内存构造/销毁/管理。

      如果您只是学习 C,那么现在最好还是坚持使用 char 数组,而不是直接研究指针,除非您知道自己在做什么,或者至少不做一点研究/学习提前。

      数组不同于指针,有一个帖子here 详细说明了区别。

      针对您的问题,您可以通过在代码中添加更多输出来更清楚地了解发生了什么。使用%p 格式说明符,您可以打印出变量的指针位置。

      有几种方法可以调试 C 程序来找出这些东西,例如在 Linux 上使用 gdb 或在 Windows 上使用 Visual Studio,但这是在这里展示的最简单方法。

      在你的程序中添加一些调试输出,你会得到:

      #include<stdio.h>
      
      struct my_struct{
        char *name;
      };
      
      int main()
      {
      
        struct my_struct arr[3];
        int i = 0;
        char str[10];
      
        while (i<3)
        {
          fgets(str, 10, stdin);
          arr[i].name = str;
          printf("Array number %d: %s", i, arr[i].name); 
          printf("  %p\n", arr[i].name);
          i++;
        }
      
        printf("\n1 - %s (%p)\n2 - %s (%p)\n3 - %s (%p)", 
          arr[0].name, arr[0].name, 
          arr[1].name, arr[1].name,
          arr[2].name, arr[2].name);
      
        return 0;
      }
      

      这会产生以下输出(给定 johnjacobjingle 作为输入):

      Array number 0: john
        0xfff8d96a
      Array number 1: jacob
        0xfff8d96a
      Array number 2: jingle
        0xfff8d96a
      
      1 - jingle
       (0xfff8d96a)
      2 - jingle
       (0xfff8d96a)
      3 - jingle
       (0xfff8d96a)
      

      由此我们可以看出,您每次都明显地覆盖了相同的内存地址。这样做的原因是因为name 被分配给str,更迂腐的是,name 被设置为内存中存在char* str 的位置,这可能不会改变.这与循环执行 3 次 x = 3 基本相同。

      要解决此问题,您需要做两件事。

      1. 在使用之前分配每个arr[i].name 实例。这可以使用来自stdlib.hmalloccalloc 来实现。
      2. 将从stdin 检索到的输入复制到arr[i].name。这可以使用来自string.hstrcpystrncpy(首选)来实现

      应用修复(两行代码)后,您的循环将如下所示:

      while (i<3)
      {
        fgets(str, 10, stdin);
      
        // Allocate 10 (most likely) bytes of memory to arr[i].name
        // And also clear out that memory space      
        arr[i].name = (char*)calloc(10, sizeof(char));
      
        // Safely copy the data (max 10 chars) from 'str' into 'arr[i].name'
        strncpy(arr[i].name, str, 10);
      
        printf("Array number %d: %s", i, arr[i].name); 
        printf("  %p\n", arr[i].name);
      
        i++;
      }
      

      应用修复后,最终结果是这样的:

      Array number 0: john
        0x89a0008
      Array number 1: jacob
        0x89a0018
      Array number 2: jingle
        0x89a0028
      
      1 - john
       (0x89a0008)
      2 - jacob
       (0x89a0018)
      3 - jingle
       (0x89a0028)
      

      【讨论】:

      • 我的疑问在你回答之前就解决了,所以我已经接受了。不过,您的内容非常清晰且很有帮助。多谢!! +1
      • 我认为您的第一个示例生成的输出不正确。最终打印显示名称不同,但应该相同。
      • @Ben,你说得对,我不小心把正确的答案复制了两次。谢谢!
      猜你喜欢
      • 1970-01-01
      • 2021-03-20
      • 2013-02-03
      • 2015-10-21
      • 1970-01-01
      • 2011-11-07
      • 2019-02-13
      • 1970-01-01
      • 2011-04-28
      相关资源
      最近更新 更多