C语言中结构体赋值问题的讨论
今天帮师姐调一个程序的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 语言的发展史来看,有时间这块内容我再补上!
参考资料:
- 维基百科
- Stackoverflow
- International Standard ISO/IEC 9899
C语言中结构体赋值问题的讨论的更多相关文章
- C语言中结构体赋值问题的讨论(转载)
今天帮师姐调一个程序的BUG,师姐的程序中有个结构体直接赋值的语句,在我印象中结构体好像是不能直接赋值的,正如数组不能直接赋值那样,我怀疑这个地方有问题,但最后证明并不是这个问题.那么就总结一下C语言 ...
- C语言中结构体对齐问题
C语言中结构体对齐问题 收藏 关于C语言中的结构体对齐问题 1,比如: struct{short a1;short a2;short a3;}A;struct{long a1;short a2;}B; ...
- 6. Go 语言中结构体的使用
1. 结构体的定义格式 在go语言中结果的定义格式如下: 123 type structName struct { filedList} 列子如下: 1234 type Person struct { ...
- Go语言中结构体的使用-第2部分OOP
1 概述 结构体的基本语法请参见:Go语言中结构体的使用-第1部分结构体.结构体除了是一个复合数据之外,还用来做面向对象编程.Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性. ...
- Go语言中结构体的使用-第1部分结构体
1 概述 结构体是由成员构成的复合类型.Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性.结构体成员,也可称之为成员变量,字段,属性.属性要满足唯一性.结构体的概念在软件工程上 ...
- C语言中结构体的深拷贝和浅拷贝
C++ 的浅拷贝和深拷贝(结构体) 拷贝有两种:深拷贝,浅拷贝 浅拷贝:拷贝过程中是按字节复制的,对于指针型成员变量只复制指针本身,而不复制指针所指向的目标 (1)结构体中不存在指针成员变量时 typ ...
- C语言中结构体变量之间赋值
近期,我阅读了某新员工小刘写的C语言代码,发现其对结构体变量之间的赋值不是非常熟悉. 对于两个同样类型的结构体变量,他均採用的是逐个成员变量直接赋值的形式.例如以下的代码演示样例: 如上代码所看到的, ...
- C语言中结构体參数变量的传递
[文章摘要] 在C语言中,结构体參数变量常常作为函数的參数来进行传递.但假设參数设置不当.会出现内存问题. 本文以实际的程序代码为例.具体地介绍怎样正确地使用结构体參数变量.为相关的开发工作提供了參考 ...
- c语言中结构体指针
1.指向结构体的指针变量: C 语言中->是一个总体,它是用于指向结构体,如果我们在程序中定义了一个结构体,然后声明一个指针变量指向这个结构体.那么我们要用指针取出结构体中的数据.就要用到指向运 ...
随机推荐
- 【手记】F5调试报"由于缺少调试目标xxx无法开始调试xxx设置OutputPath和AssemblyName"
问题如图: 如果先点[生成]项目,再按F5调试就不会报错,但稍后修改代码后再次调试,会发现VS启动的是代码修改前的程序,也就是说,深一层的问题其实是调试时不会先生成,而是直接打开debug/relea ...
- sqlserver 多库查询 sp_addlinkedserver使用方法(添加链接服务器)
sqlserver 多库查询 sp_addlinkedserver使用方法(添加链接服务器) 我们日常使用SQL Server数据库时,经常遇到需要在实例Instance01中跨实例访问Instanc ...
- 扩展lamda表达中distinct按照字段去除重复
首先,我们定义一个Student类来测试. public class Student { public int ID { get; set; } public string Name { get; s ...
- Bootstrap之表单控件状态
Bootstrap中的表单控件状态主要有三种:焦点状态,禁用状态,验证状态. 一.焦点状态:该状态告诉用户可输入或选择东西 焦点状态通过伪类“:focus”以实现. bootstrap.css相应 ...
- 小议jQuery插件开发
1.写在前面: 大家都知道PHP专注后台与数据库的交互,前端页面中是js的天下,而jQuery作为使用最广泛,最简单有效的js的框架.深受大家的喜欢. 而js作为一门面向对象的开发语言,它独特的语法和 ...
- Lind.DDD.Events事件总线~自动化注册
回到目录 让大叔兴奋的自动化注册 对于领域事件之前说过,在程序启动时订阅(注册)一些事件处理程序,然后在程序的具体位置去发布(触发)它,这是传统的pub/sub模式的体现,当然也没有什么问题,为了让它 ...
- FIS3的简单使用
序言: 最近在收集前端的优化工具,随便一搜,厉害了word哥,有grunt.gulp.FIS3.webpack等等,简直就是眼花缭乱!前辈们对于他们的评价各有千秋,于是乎就想每个都来用一遍(之前已经倒 ...
- html基础大全(经典)
HTML教程 一.HTML基础(人) 1.基础概念: 1.1.自动闭合标签和闭合标签(如出错html编写时不会报错) 自闭合:<mete /> 闭合:<table>文字< ...
- JavaScript函数的4种调用方法详解
在JavaScript中,函数是一等公民,函数在JavaScript中是一个数据类型,而非像C#或其他描述性语言那样仅仅作为一个模块来使用.函数有四种调用模式,分别是:函数调用形式.方法调用形式.构造 ...
- React Native常用组件Image使用
前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...