今天帮师姐调一个程序的BUG,师姐的程序中有个结构体直接赋值的语句,在我印象中结构体好像是不能直接赋值的,正如数组不能直接赋值那样,我怀疑这个地方有问题,但最后证明并不是这个问题。那么就总结一下C语言中结构体赋值的问题吧:

结构体直接赋值的实现

下面是一个实例:

#include <stdio.h>

struct Foo {
char a;
int b;
double c;
}foo1, foo2; //define two structs with three different fields void struct_assign(void)
{
foo2 = foo1; //structure directly assignment
} int main()
{
foo1.a = 'a';
foo1.b = 1;
foo1.c = 3.14;
struct_assign();
printf("%c %d %lf\n", foo2.a, foo2.b, foo2.c); return 0;
}

我在Ubuntu 13.04下使用gcc 4.7.3 编译运行得到的结果,如下所示:

guohl@guohailin:~/Documents/c$ gcc struct_test1.c -o struct_test1
guohl@guohailin:~/Documents/c$ ./struct_test1
a 1 3.140000

可以从结果上看出,结构体直接赋值在C语言下是可行的,我们看看struct_assign()函数的汇编实现,从而从底层看看C语言是如何实现两个结构体之间的赋值操作的:

struct_assign:
pushl %ebp
movl %esp, %ebp
movl foo1, %eax
movl %eax, foo2 //copy the first 4 bytes from foo1 to foo2
movl foo1+4, %eax
movl %eax, foo2+4 //copy the second 4 bytes from foo1 to foo2
movl foo1+8, %eax
movl %eax, foo2+8 //copy the third 4 bytes from foo1 to foo2
movl foo1+12, %eax
movl %eax, foo2+12 //copy the forth 4 bytes from foo1 to foo2
popl %ebp
ret

这段汇编比较简单,由于结构体的对齐的特性,sizeof(srtruct Foo)=16,通过四次movl操作将foo1的结构体内容拷贝到结构体foo2中。从汇编上看出,结构体赋值,采用的类似于memcpy这种形式,而不是逐个字段的拷贝。

复杂结构体的赋值

如果结构体中含有其它复杂数据类型呢,例如数组、指针、结构体等,从上面的汇编实现可以看出,只要两个结构体类型相同,就可以实现赋值,如下例:

#include <stdio.h>

struct Foo {
int n;
double d[2];
char *p_c;
}foo1, foo2; int main()
{
char *c = (char *) malloc (4*sizeof(char));
c[0] = 'a'; c[1] = 'b'; c[2] = 'c'; c[3] = '\0'; foo1.n = 1;
foo1.d[0] = 2; foo1.d[1] = 3;
foo1.p_c = c; foo2 = foo1; //assign foo1 to foo2 printf("%d %lf %lf %s\n", foo2.n, foo2.d[0], foo2.d[1], foo2.p_c); return 0;
}

运行结果如下:

guohl@guohailin:~/Documents/c$ gcc struct_test2.c -o struct_test2
guohl@guohailin:~/Documents/c$ ./struct_test2
1 2.000000 3.000000 abc

可以看出结果和我们想象的是一样的。再次验证结构体的赋值,是直接结构体的内存的拷贝!但正是这个问题,如上面的实例,foo1 和 foo2 中p_c 指针都是指向我们申请的一块大小为4个字节的内存区域,这里注意的是,结构体的拷贝只是浅拷贝,即指针p_c的赋值并不会导致再申请一块内存区域,让foo2的p_c指向它。那么,如果释放掉foo1中的p_c指向的内存,此时foo2中p_c变成野指针,这是对foo2的p_c操作就会出现一些不可预见的问题!在C++中引入了一种可以允许用户重载结构体赋值操作运算,那么我们就可以根据语义重载赋值操作。

数组是二等公民

二等公民在维基百科上的解释是:

二等公民不是一个正式的术语,用来描述一个社会体系内对一部分人的歧视或对外来人口的政治限制,即使他们作为一个公民或合法居民的地位。 二等公民虽然不一定是奴隶或罪犯,但他们只享有有限的合法权利、公民权利和经济机会,并经常受到虐待或忽视。法律无视二等公民,不向他们提供保护,甚至在制订法律时可能会根本不考虑他们的利益。划分出二等公民的行为,普遍被视为一种侵犯人权的行为。 典型的二等公民所面临的障碍包括但不仅限于(缺乏或丧失表决权):权利被剥夺,限制民事或军事服务(不包括任何情况下的征兵),以及限制,语言,宗教,教育,行动和结社的自由,武器的所有权,婚姻,性别认同和表达,住房和财产所有权 。

从词条上解释可以看出二等公民与一等公民在权利上是有差别的,这个词很有意思作为计算机专业术语,其含义也有异曲同工之妙!同样我们看看维基百科对计算机的术语”first-class citizen"(一等公民)的定义,一般要满足以下几点,

  • can be stored in variables and data structures
  • can be passed as a parameter to a subroutine
  • can be returned as the result of a subroutine
  • can be constructed at run-time
  • has intrinsic identity (independent of any given name)

对比着上面的定义来看C语言数组,数组作为一个函数的参数传递时,退化成一个指针; 同时,数组无法作为函数的返回值; 也许让数组更不服气的是,数组之间不能直接赋值操作,如下面的操作就是非法的:

int a[10];
int b[10];
a = b;

但是如果数组包装在结构体中,那么就能进行赋值了!相比之下,结构体可以作为函数参数和返回值,这就是一等公民的待遇!至于为什么数组必须是二等公民,这是有历史原因的,大家可以参考C 语言的发展史来看,有时间这块内容我再补上!


参考资料:

C语言中结构体赋值问题的讨论的更多相关文章

  1. C语言中结构体赋值问题的讨论(转载)

    今天帮师姐调一个程序的BUG,师姐的程序中有个结构体直接赋值的语句,在我印象中结构体好像是不能直接赋值的,正如数组不能直接赋值那样,我怀疑这个地方有问题,但最后证明并不是这个问题.那么就总结一下C语言 ...

  2. C语言中结构体对齐问题

    C语言中结构体对齐问题 收藏 关于C语言中的结构体对齐问题 1,比如: struct{short a1;short a2;short a3;}A;struct{long a1;short a2;}B; ...

  3. 6. Go 语言中结构体的使用

    1. 结构体的定义格式 在go语言中结果的定义格式如下: 123 type structName struct { filedList} 列子如下: 1234 type Person struct { ...

  4. Go语言中结构体的使用-第2部分OOP

    1 概述 结构体的基本语法请参见:Go语言中结构体的使用-第1部分结构体.结构体除了是一个复合数据之外,还用来做面向对象编程.Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性. ...

  5. Go语言中结构体的使用-第1部分结构体

    1 概述 结构体是由成员构成的复合类型.Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性.结构体成员,也可称之为成员变量,字段,属性.属性要满足唯一性.结构体的概念在软件工程上 ...

  6. C语言中结构体的深拷贝和浅拷贝

    C++ 的浅拷贝和深拷贝(结构体) 拷贝有两种:深拷贝,浅拷贝 浅拷贝:拷贝过程中是按字节复制的,对于指针型成员变量只复制指针本身,而不复制指针所指向的目标 (1)结构体中不存在指针成员变量时 typ ...

  7. C语言中结构体变量之间赋值

    近期,我阅读了某新员工小刘写的C语言代码,发现其对结构体变量之间的赋值不是非常熟悉. 对于两个同样类型的结构体变量,他均採用的是逐个成员变量直接赋值的形式.例如以下的代码演示样例: 如上代码所看到的, ...

  8. C语言中结构体參数变量的传递

    [文章摘要] 在C语言中,结构体參数变量常常作为函数的參数来进行传递.但假设參数设置不当.会出现内存问题. 本文以实际的程序代码为例.具体地介绍怎样正确地使用结构体參数变量.为相关的开发工作提供了參考 ...

  9. c语言中结构体指针

    1.指向结构体的指针变量: C 语言中->是一个总体,它是用于指向结构体,如果我们在程序中定义了一个结构体,然后声明一个指针变量指向这个结构体.那么我们要用指针取出结构体中的数据.就要用到指向运 ...

随机推荐

  1. 芒果TV招聘研发工程师(JAVA PYTHON),地点长沙

    长沙芒果TV招聘高级 JAVA Python 工程师,工作地点:湖南广电   有兴趣的邮件0xmalloc@gmail.com; zealotyin@qq.com 公司有一大批从北京上海一线互联网企业 ...

  2. struts2的action是多例,servlet是单例

    struts2中action是多例的,即一个session产生一个action如果是单例的话,若出现两个用户都修改一个对象的属性值,则会因为用户修改时间不同,两个用户访问得到的 属性不一样,操作得出的 ...

  3. css实现小三角(原理)

    效果图如图1所示:(简单示范,有点丑,莫介意) PS:兼容IE,FF , chrome ,360安全浏览器 先讲下原理吧,如图2所示: 这个div的样式如下所示: div{ width: 0px; h ...

  4. java timer 执行任务

    1. 建立timer import java.util.Timer; import java.util.TimerTask; public class Start { public class Sta ...

  5. SSH(Struts2+Spring+Hibernate)框架搭建流程<注解的方式创建Bean>

    此篇讲的是MyEclipse9工具提供的支持搭建自加包有代码也是相同:用户登录与注册的例子,表字段只有name,password. SSH,xml方式搭建文章链接地址:http://www.cnblo ...

  6. P2P核心业务体系

    看似一个简单的充值.提现按钮,背后可是巨多的逻辑和业务处理.

  7. wParam和lParam两个参数到底是什么意思?

    在Windows的消息函数中,有两个非常熟悉的参数:wParam,lParam. 这两个参数的字面意义对于现在的程序来说已经不重要了,因为它是16位系统的产物,为了保持程序的可移植性,就将它保存了下来 ...

  8. MongoDB基础入门002--基本操作,增删改查

    一.这里只是演示最基本的操作,更多的信息可以去官网.https://docs.mongodb.com/manual 打开一个cmd,输入mongo命令打开shell,其实这个shell就是mongod ...

  9. 有问题得就分享(此实现不是 Windows 平台 FIPS 验证的加密算法的一部分)

    今天呢,我写完了MD5加密算法的代码,可是我正高兴的想运行时,按了F5.突然了个这样的窗体 我当时就纳闷了,怎么回事? 代码又没有错,这是乍回事! 于是呢上网查了看看:可以这样解决的 在window中 ...

  10. Java基础学习 -- 异常

    当异常发生时,原本要接着执行的代码不再执行,转而让其他部分的代码来处理.如果没有代码负责处理,控制台会报告异常. 异常出现时的执行机制: 异常机制最大的好处是:清晰地分开了 正常的业务逻辑 和 遇到情 ...