【问题标题】:Copy a struct with a string member in C在 C 中复制带有字符串成员的结构
【发布时间】:2015-07-12 05:21:44
【问题描述】:

我有一个简单的结构,其中包含一个定义为 char 数组的字符串。我认为使用赋值运算符将结构的实例复制到另一个实例只会复制存储在 char 指针中的内存地址。相反,似乎复制了字符串内容。我整理了一个非常简单的例子:

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

struct Test{
  char str[20];
};

int main(){

  struct Test t1, t2;
  strcpy(t1.str, "Hello");
  strcpy(t2.str, "world");
  printf("t1: %s %p\n", t1.str, (char*)(t1.str));
  printf("t2: %s %p\n", t2.str, (char*)(t2.str));
  t2 = t1;
  printf("t2: %s %p\n", t2.str, (char*)(t2.str));
  return 0;
}

用 gcc 4.9.2 编译这段代码我得到:

t1: Hello 0x7fffb8fc9df0
t2: world 0x7fffb8fc9dd0
t2: Hello 0x7fffb8fc9dd0

据我了解,在t2 = t1 t2.str 指向它在分配之前指向的相同内存地址之后,但现在在该地址中,在 t1.str 中找到了相同的字符串。所以在我看来,字符串内容已自动从一个内存位置复制到另一个内存位置,这是我认为 C 不会做的事情。我认为这种行为是由我将 str 声明为 char[] 而不是 char* 的事实触发的。实际上,尝试使用 t2.str = t1.str 直接将一个字符串分配给另一个字符串会出现此错误:

Test.c: In function ‘main’:
Test.c:17:10: error: assignment to expression with array type
   t2.str = t1.str;
      ^

这让我认为在某些情况下,数组的处理方式与指针不同。我仍然无法弄清楚数组分配的规则是什么,或者换句话说,为什么当我将一个结构复制到另一个结构时会复制结构内的数组,但我不能直接将一个数组复制到另一个数组。

【问题讨论】:

  • 在某些情况下,数组的处理方式与指针不同它们是完全不同的公民。

标签: c arrays string struct


【解决方案1】:

在 C 中,struct 是编译器知道如何构造内存区域的一种方式。 struct 是一种模板或模板,C 编译器使用它来计算如何计算结构各个成员的偏移量。

最初的 C 编译器不允许 struct 赋值,因此人们不得不使用 memcpy() 函数来分配结构,但是后来的编译器允许。 C 编译器将通过复制内存的struct 区域的字节数来执行struct 分配,包括可能为从一个地址到另一个地址的地址对齐而添加的填充字节。源内存区域中发生的任何事情都将复制到目标区域。副本没有什么聪明的。它只是将这么多字节的数据从一个内存位置复制到另一个内存位置。

如果struct 中有一个字符串数组或任何类型的数组,那么整个数组将被复制,因为它是结构的一部分。

如果struct 包含指针变量,那么这些指针变量也会从一个区域复制到另一个区域。这样做的结果是您将拥有两个具有相同数据的结构。每个结构中的指针变量将具有相似的地址值,这两个区域是彼此的副本,因此一个结构中的特定指针将与另一个结构中的相应指针具有相同的地址,并且两者都将指向同一地点。

请记住,结构分配只是将数据字节从一个内存区域复制到另一个内存区域。例如,如果我们有一个简单的 struct 和一个 char 数组,其 C 源代码如下所示:

typedef struct {
    char tt[50];
} tt_struct;

void test (tt_struct *p)
{
    tt_struct jj = *p;

    tt_struct kk;

    kk = jj;
}

Visual Studio 2005 C++ 编译器在调试模式下为kk = jj; 赋值的汇编器列表输出如下所示:

; 10   :    tt_struct kk;
; 11   : 
; 12   :    kk = jj;

  00037 b9 0c 00 00 00   mov     ecx, 12            ; 0000000cH
  0003c 8d 75 c4     lea     esi, DWORD PTR _jj$[ebp]
  0003f 8d 7d 88     lea     edi, DWORD PTR _kk$[ebp]
  00042 f3 a5        rep movsd
  00044 66 a5        movsw

这段代码将数据从内存中的一个位置逐个 4 字节复制到另一个位置。使用较小的char 数组大小,编译器可能会选择使用不同系列的指令来复制内存以提高效率。

在 C 中,数组并没有真正以智能的方式处理。数组不像 Java 看待数组那样被视为数据结构。在 Java 中,数组是由对象数组组成的一种对象。在 C 中,数组只是一个内存区域,数组名称实际上被视为常量指针或不可更改的指针。结果是,在 C 中,您可以有一个数组,例如 int myInts[5];,Java 将其视为一个由五个整数组成的数组,但对于 C,它实际上是一个标签为 myInts 的常量指针。在 Java 中,如果您尝试访问超出范围的数组元素,例如 myInts[i],其中 i 的值为 8,您将收到运行时错误。在 C 语言中,如果您尝试访问超出范围的数组元素,例如 myInts[i],其中 i 的值为 8,除非您正在使用一个不错的 C 编译器进行调试构建,否则您将不会收到运行时错误。运行时检查。然而,有经验的 C 程序员倾向于将数组和指针视为相似的结构,尽管数组作为指针确实有一些限制,因为它们是常量指针的一种形式,并不完全是指针,但具有与指针相似的一些特性。

这种缓冲区溢出错误在 C 中很容易通过访问超过其元素数量的数组来实现。典型的例子是将一个 char 数组的字符串复制到另一个 char 数组中,而源 char 数组中没有零终止字符,当您期望 10 或 15 个字节时,会导致一个几百字节的字符串副本。

【讨论】:

    【解决方案2】:

    如果你运行下面的简单程序

    #include <stdio.h>
    
    int main( void )
    {
        {
            struct Test
            {
                char str[20];
            };
            printf( "%zu\n", sizeof( Test ) );
        }
    
        {
            struct Test
            {
                char *str;
            };
            printf( "%zu\n", sizeof( Test ) );
        }
        return 0;
    }
    

    你会得到类似下面的结果

    20
    4
    

    所以第一个结构包含一个由 20 个元素组成的字符数组,而第二个结构只包含一个 char * 类型的指针。

    当一个结构被分配给另一个结构时,它的数据成员被复制。因此,对于第一个结构,数组的所有内容都被复制到另一个结构中。对于第二个结构,仅复制指针的值(它包含的地址)。指针指向的内存不会被复制,因为它不包含在结构本身中。

    虽然通常表达式中的数组名称(极少数例外)被转换为指向其第一个元素的指针,但数组不是指针。

    【讨论】:

      【解决方案3】:

      在您的情况下实际上有 20 个字符,就像您将结构声明为 struct Test {char c1, char c2, ...}

      如果您只想复制指向字符串的指针,您可以更改结构声明如下,并通过函数Test_initTest_delete 手动管理字符串的内存。

      struct Test{
        char* str;
      };
      
      void Test_init(struct Test* test, size_t len) {
        test->str = malloc(len);
      }
      
      void Test_delete(struct Test* test) {
        free(test->str);
      }
      

      【讨论】:

        【解决方案4】:

        该结构不包含指针,但包含 20 个字符。 在t2 = t1之后,将t1的20个字符复制到t2中。

        【讨论】:

        • 是的,你是对的,我正在做一些实验,即将得出相同的结论。谢谢!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-11
        • 2012-12-12
        • 1970-01-01
        • 2011-08-11
        • 2016-01-27
        • 2013-12-26
        相关资源
        最近更新 更多