简体   繁体   English

在结构中重新分配元素(尤其是指针)

[英]Reassigning elements (pointers in particular) in a struct

I want to take a struct, "trash" it, and reassign the values of its elements. 我想要一个结构,对其进行“删除”,然后重新分配其元素的值。 I wrote the following example code, and it seems to work. 我编写了以下示例代码,它似乎可以工作。 But I am wondering if anyone sees anything wrong with it. 但是我想知道是否有人认为这有什么问题。 I am mainly worried about the "name" and "ip" pointers. 我主要担心“名称”和“ ip”指针。 They came out looking right, but did I just get lucky? 他们出来看起来不错,但我是否很幸运?

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


typedef struct stuff_s{

   int n;
   char *name;
   int dsize;
   int *ip;

} stuff;


void output_stuff(stuff *s){

   int n;

   printf("%d\n", s->n);
   printf("%s\n", s->name);
   printf("%d\n", s->dsize);

   for (n=0; n<s->dsize; n++) {
      printf("%d ",s->ip[n]);
   }

   printf("\n");

}


void reassign(stuff *s) {

   stuff snew;
   int n;

   snew.n = 2;
   snew.name = calloc(32, sizeof(char));
   strcpy(snew.name, "Snoopy");
   snew.dsize = 12;
   snew.ip = calloc(snew.dsize, sizeof(int));
   for (n=0; n<snew.dsize; n++) { snew.ip[n] = n; }

   free(s->name);
   free(s->ip);

   memcpy(s, &snew, sizeof(stuff));

}


int main() {

   stuff d;
   int n;

   d.n = 1;
   d.dsize = 10;
   d.ip = calloc(d.dsize, sizeof(int));
   for (n=0; n<d.dsize; n++) {
      d.ip[n] = n;
   } 
   d.name = calloc(32, sizeof(char));
   strcpy(d.name,"Charlie Brown");

   output_stuff(&d);
   printf("\n");

   reassign(&d);

   output_stuff(&d);

   free(d.ip);
   free(d.name);

}

Well, here's what valgrind says: 好吧,这是valgrind所说的:

1
--2097-- REDIR: 0x4ebf9c0 (strlen) redirected to 0x4c2e0e0 (strlen)
Charlie Brown
10
0 1 2 3 4 5 6 7 8 9

--2097-- REDIR: 0x4eb9d10 (free) redirected to 0x4c2bd80 (free)
--2097-- REDIR: 0x4ec8630 (memcpy@@GLIBC_2.14) redirected to 0x4a25720 (_vgnU_ifunc_wrapper)
--2097-- REDIR: 0x4ed1620 (__memcpy_sse2_unaligned) redirected to 0x4c2f6b0 (memcpy@@GLIBC_2.14)
2
Snoopy
12
0 1 2 3 4 5 6 7 8 9 10 11
==2097==
==2097== HEAP SUMMARY:
==2097==     in use at exit: 0 bytes in 0 blocks
==2097==   total heap usage: 4 allocs, 4 frees, 152 bytes allocated
==2097==
==2097== All heap blocks were freed -- no leaks are possible
==2097==
==2097== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==2097== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

So there are no memory errors. 因此,没有内存错误。

Some stylistic points 一些风格点

  1. You don't need to use sizeof(char) - it's defined to be 1 您不需要使用sizeof(char) -它定义为1
  2. dsize should be unsigned, and should really be a size_t , which is bigger than an int dsize应该是无符号的,并且实际上应该是size_t ,它大于int
  3. As SoronellHaetir points out, you don't need to use another struct - you can modify the passed-in one 正如SoronellHaetir指出的那样,您不需要使用其他结构-您可以修改传入的结构

did I just get lucky? 我只是幸运吗?

Well, you got lucky, but not for the reasons you may be thinking... 好吧,您很幸运,但并非出于您可能正在思考的原因...

Your handling of the stuct allocation and reassignment was correct, but incomplete. 您对结构分配和重新分配的处理是正确的,但不完整。 You should validate all memory allocations to insure you do not invoke Undefined Behavior by attempting to access memory that does not belong to you in the event of an allocation failure. 您应该验证所有内存分配,以确保在分配失败的情况下尝试访问不属于您的内存,从而确保您不会调用未定义行为

For example, when you allocate d.ip -- check the return to insure calloc succeeded, eg 例如,当您分配d.ip - 检查返回值以确保calloc成功,例如

    if (!(d.ip = calloc (d.dsize, sizeof *d.ip))) {
        perror ("calloc d.ip failed");
        exit (EXIT_FAILURE);
    }

You should do that for every allocation. 您应该为每个分配执行此操作。

Type void for reassign is insufficient. 输入void进行reassign是不够的。 It provides no meaningful way to determine whether the allocations in the function succeeded or not. 它没有提供有意义的方法来确定函数中的分配是否成功。 Any time you are allocating memory, opening a file, taking input, etc.. make sure you declare your function with a type that can provide a meaningful return to indicate success or failure . 每当您分配内存,打开文件,输入内容等时,请确保使用可以提供有意义的返回值来指示成功失败的类型声明函数。 A pointer returning a valid address or NULL works, as does an integer type returning a value that can indicate success/failure. 返回有效地址或NULL指针可以工作,返回值可以指示成功/失败的整数类型也可以。

Don't use magic numbers in your code. 不要在代码中使用幻数。 If you need a constant for instance for the size of the name member, then #define one or use an enum . 例如,如果您需要一个常量作为name成员的大小,则#define一个或使用enum For example if you want the size of name to be 32 bytes, then define a namesize constant, eg 例如,如果您希望name的大小为32个字节,则定义一个name大小常量,例如

#define NMSZ 32

Your reassign function continues with magic numbers and magic strings in making assignments to .n , .dsize , and .dname . reassign功能继续与幻数魔法字符串进行分配,以.n.dsize.dname (granted, I understand that is was likely for a quick test). (当然,我知道可以进行快速测试)。 Make reassign useful by passing the values of n , name and dsize as function parameters. 通过将nnamedsize的值作为函数参数传递,使reassign很有用。

You don't need to memcpy (s, &snew, sizeof (stuff)); 您不需要进行memcpy (s, &snew, sizeof (stuff)); . All you need to do is assign *s = snew; 您需要做的就是分配*s = snew; . Now you may be a bit lucky here since you limited the pointer members of your struct to single pointers. 现在您可能在这里有点幸运,因为您将结构的指针成员限制为单个指针。 Since the value contained is the address of the start of each memory block, an assignment is all that is required. 由于包含的值是每个存储块开始的地址,因此只需要分配即可。 However, if you use a pointer to pointer to type as a member (eg a double-pointer) an assignment (or a memcpy ) is no longer sufficient, and a deep copy will be required. 但是,如果使用指向对象的成员的指针(例如,双指针),分配(或memcpy )将不再足够,并且将需要深拷贝。

note: unless you need name to be modifiable (eg you need to change individual characters), then a string literal can be used and eliminate the need to dynamically allocate altogether. 注意:除非您需要name是可修改的(例如,您需要更改单个字符),否则可以使用字符串文字 ,而无需完全动态分配。

It may also be wise to change your choice of int n; 更改int n;的选择可能也是明智的int n; to int i; int i; as the counter to eliminate confusion between n and sn , etc.. 作为消除nsn等之间混淆的计数器。

Putting those pieces together, you could make reassign a bit more robust as follows: 将这些部分放在一起,您可以使reassign更加稳健,如下所示:

stuff *reassign (stuff *s, int n, const char *name, int dsize) {

    stuff snew;
    int i;

    snew.n = n; /* validate all memory allocations */
    if (!(snew.name = calloc (NMSZ, sizeof *snew.name))) {
        perror ("calloc snew.name");
        return NULL;
    }
    strcpy (snew.name, name);

    snew.dsize = dsize; /* ditto */
    if (!(snew.ip = calloc (snew.dsize, sizeof *snew.ip))) {
        perror ("calloc snew.ip");
        return NULL;
    }

    for (i = 0; i < snew.dsize; i++)
        snew.ip[i] = i + 5;

    free (s->name);
    free (s->ip);

    *s = snew;  /* you can simply assign snew */

    return s;
}

While you are free to assign each member of your struct after declaration, beginning with C99, use of named-initializers make a simple and readable way to initialize non-dynamic members at the time of declaration, eg 尽管您可以在声明之后自由分配结构的每个成员,但从C99开始,使用named-initializers可以在声明时初始化非动态成员的简单易懂的方式,例如

    stuff d = { .n = 1, .dsize = 10 };

Finally, as noted in the comments, there is no need to call printf ("\\n"); 最后,如注释中所述,无需调用printf ("\\n"); to output a single character. 输出单个字符。 That is what putchar ('\\n'); 那就是putchar ('\\n'); is for. 是为了。 Granted, a decent compiler will make the optimization for you, but that isn't an excuse for not using the correct tool for the job to begin with. 诚然,一个不错的编译器会为您进行优化,但这并不是没有为开始使用正确的工具的借口。

Putting all the pieces together, you could make your code a bit more robust and protect against Undefined Behavior by adding validations and making use of the meaningful return from reassign as follows: 将所有部分放在一起,可以通过添加验证并利用reassign的有意义的收益来使代码更健壮,并防止未定义行为 ,如下所示:

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

#define NMSZ 32

typedef struct stuff_s {
    int n,
        dsize,
        *ip;
    char *name;
} stuff;


void output_stuff (stuff *s){

    int n;

    printf ("%d\n%s\n%d\n", s->n, s->name, s->dsize);

    for (n = 0; n < s->dsize; n++)
        printf ("%d ",s->ip[n]);

    putchar ('\n');
}


stuff *reassign (stuff *s, int n, const char *name, int dsize) {

    stuff snew;
    int i;

    snew.n = n; /* validate all memory allocations */
    if (!(snew.name = calloc (NMSZ, sizeof *snew.name))) {
        perror ("calloc snew.name");
        return NULL;
    }
    strcpy (snew.name, name);

    snew.dsize = dsize; /* ditto */
    if (!(snew.ip = calloc (snew.dsize, sizeof *snew.ip))) {
        perror ("calloc snew.ip");
        return NULL;
    }

    for (i = 0; i < snew.dsize; i++)
        snew.ip[i] = i + 5;

    free (s->name);
    free (s->ip);

    *s = snew;  /* you can simply assign snew */

    return s;
}

int main() {

    stuff d = { .n = 1, .dsize = 10 };
    int i;

    if (!(d.ip = calloc (d.dsize, sizeof *d.ip))) {
        perror ("calloc d.ip failed");
        exit (EXIT_FAILURE);
    }
    for (i = 0; i < d.dsize; i++)
        d.ip[i] = i;

    if (!(d.name = calloc (NMSZ, sizeof *d.name))) {
        perror ("calloc d.name failed");
        exit (EXIT_FAILURE);
    }
    strcpy (d.name, "Charlie Brown");

    output_stuff (&d);
    putchar ('\n');

    if (reassign (&d, 2, "Linus", 12)) {

        output_stuff (&d);

        free (d.ip);
        free (d.name);
    }

    return 0;
}

Example Use/Output 使用/输出示例

$ ./bin/structcpy
1
Charlie Brown
10
0 1 2 3 4 5 6 7 8 9

2
Linus
12
5 6 7 8 9 10 11 12 13 14 15 16

Look thins over an let me know if you have any further questions. 如果您有其他疑问,请随时告诉我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM