首页 文章

为什么C支持在结构中成员分配数组,但一般不支持?

提问于
浏览
77

我理解不支持成员分配数组,因此以下方法不起作用:

int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"

我只是接受了这个事实,认为语言的目的是提供一个开放式框架,并让用户决定如何实现诸如复制数组之类的东西 .

但是,以下工作正常:

struct myStruct {int num[3];};
myStruct struct1={{1,2,3}};
myStruct struct2;
struct2 = struct1;

数组 num[3] 是成员方式,从 struct1 中的实例分配到 struct2 中的实例 .

为什么结构支持成员方式的数组,但一般情况下不支持?

editRoger Pate 在帖子中的评论std::string in struct - Copy/assignment issues?似乎指向答案的大致方向,但我不知道自己确认 .

edit 2 :很多出色的回应 . 我之所以选择_221760,是因为我主要想知道这种行为背后的哲学或历史原理,但 James McNellis 对相关规范文档的引用也很有用 .

4 回答

  • 26

    我知道,每个回答的人都是C / C的专家 . 但我想,这是主要原因 .

    num2 = num1;

    在这里,您尝试更改阵列的基址,这是不允许的 .

    当然,struct2 = struct1;

    这里,对象struct1被分配给另一个对象 .

  • 0

    关于赋值运算符,C标准如下(C03§5.17/ 1):

    有几个赋值运算符...都需要一个可修改的左值作为它们的左操作数

    数组不是可修改的左值 .

    但是,特别定义了对类类型对象的赋值(§5.17/ 4):

    对类的对象的赋值由复制赋值运算符定义 .

    因此,我们期待查看类的隐式声明的复制赋值运算符(§12.8/ 13):

    类X的隐式定义的复制赋值运算符执行其子对象的成员分配 . ...每个子对象都以适合其类型的方式分配:... - 如果子对象是一个数组,则以适合于元素类型的方式分配每个元素...

    因此,对于类类型对象,正确复制数组 . 请注意,如果您提供用户声明的复制赋值运算符,则无法利用此功能,并且您必须逐个元素地复制数组 .


    推理在C(C99§6.5.16/ 2)中类似:

    赋值运算符应具有可修改的左值作为其左操作数 .

    §6.3.2.1/ 1:

    一个可修改的左值是一个没有数组类型的左值... [其他约束如下]

    在C中,赋值比C(§6.5.16.1/ 2)简单得多:

    在简单赋值(=)中,右操作数的值将转换为赋值表达式的类型,并替换存储在左操作数指定的对象中的值 .

    对于struct-type对象的赋值,左右操作数必须具有相同的类型,因此右操作数的值只是复制到左操作数中 .

  • 39

    在这个链接:http://www2.research.att.com/~bs/bs_faq2.html有一个关于数组赋值的部分:

    数组的两个基本问题是

    • 数组不知道自己的大小

    • 数组的名称在最轻微的激发时转换为指向其第一个元素的指针

    我认为这是数组和结构之间的根本区别 . 数组变量是具有有限自我知识的低级数据元素 . 从根本上说,它是一块记忆和一种索引的方式 .

    因此,编译器无法区分int a [10]和int b [20]之间的区别 .

    然而,结构不具有相同的模糊性 .

  • 2

    这是我的看法:

    C语言的开发提供了对C中数组类型演变的一些见解:

    我将尝试概述数组的事情:

    C的先行者B和BCPL没有明显的数组类型,声明如下:

    auto V[10] (B)
    or 
    let V = vec 10 (BCPL)
    

    将V声明为一个(无类型)指针,该指针被初始化为指向未使用的10 "words"内存区域 . B已使用 * 进行指针解除引用并使用 [] 简写符号, *(V+i) 表示 V[i] ,就像今天的C / C.但是, V 不是一个数组,它仍然是一个必须指向某个内存的指针 . 当Dennis Ritchie试图用结构类型扩展B时,这会引起麻烦 . 他希望数组成为结构的一部分,就像今天的C一样:

    struct {
        int inumber;
        char name[14];
    };
    

    但是使用B,BCPL数组作为指针的概念,这将要求 name 字段包含一个指针,该指针必须在运行时初始化为结构中14字节的内存区域 . 初始化/布局问题最终通过为数组提供特殊处理来解决:编译器将跟踪数组在结构中,堆栈上的位置等,而实际上不需要指向数据的指针来实现,除了涉及数组的表达式 . 这种处理方法几乎允许所有B代码仍然运行,并且是该代码的来源"arrays convert to pointer if you look at them"规则 . 这是一个兼容性的黑客,结果非常方便,因为它允许开放大小的阵列等 .

    这里是我的猜测为什么不能分配数组:由于数组是B中的指针,你可以简单地写:

    auto V[10];
    V=V+5;
    

    改变"array" . 这现在毫无意义,因为数组变量的基数不再是左值 . 所以这个分配是不允许的,这有助于捕获在声明的数组上进行这种变基的少数程序 . 然后这个概念陷入困境:由于数组从未设计为C类型系统的第一类城市化,它们大多被视为特殊的野兽,如果你使用它们就会成为指针 . 从某个角度来看(忽略了C阵列是一个拙劣的黑客攻击),禁止数组赋值仍然有一定意义:开放数组或数组函数参数被视为没有大小信息的指针 . 编译器没有为它们生成数组赋值的信息,并且出于兼容性原因需要指针赋值 . 为声明的数组引入数组赋值会引入错误,虽然是虚假的分配(是a = ba指针赋值还是元素副本?)和其他麻烦(如何通过值传递数组?)而不实际解决问题 - 只需制作一切用memcpy显式!

    /* Example how array assignment void make things even weirder in C/C++, 
       if we don't want to break existing code.
       It's actually better to leave things as they are...
    */
    typedef int vec[3];
    
    void f(vec a, vec b) 
    {
        vec x,y; 
        a=b; // pointer assignment
        x=y; // NEW! element-wise assignment
        a=x; // pointer assignment
        x=a; // NEW! element-wise assignment
    }
    

    当1978年的C修订版增加了结构分配(http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf)时,这并没有改变 . 即使记录是C中的不同类型,也不可能在早期的K&R C中分配它们 . 您必须使用memcpy将它们成员复制,并且您只能将指针作为函数参数传递给它们 . Assigment(和参数传递)现在简单地定义为struct 's raw memory and since this couldn't的memcpy,它可以打破现有的代码 . 作为一种意想不到的副作用,这隐含地引入了某种类型的数组赋值,但是它在结构中的某处发生了,所以这不能真正引入数组使用方式的问题 .

相关问题