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

问题描述 投票:80回答:4

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

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

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

但是,以下工作正常:

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

数组num[3]是从struct1中的实例成员分配到struct2中的实例。

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

编辑:罗杰佩特在线程std::string in struct - Copy/assignment issues?中的评论似乎指向答案的大致方向,但我不知道自己确认。

编辑2:许多出色的回应。我选择Luther Blissett是因为我主要想知道这种行为背后的哲学或历史原理,但James McNellis对相关规范文档的引用也很有用。

c++ c arrays variable-assignment struct
4个回答
42
投票

这是我的看法:

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

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

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

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

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

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

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

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

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

改变一个“数组”。这现在毫无意义,因为数组变量的基数不再是左值。所以这个分配是不允许的,这有助于捕获在声明的数组上进行这种变基的少数程序。然后这个概念卡住了:因为数组从来没有被设计为C类型系统的第一类城市化,所以它们大多被视为特殊的野兽,如果你使用它们就会成为指针。从某个角度来看(忽略了C阵列是一个拙劣的黑客攻击),禁止数组赋值仍然有一定道理:开放数组或数组函数参数被视为没有大小信息的指针。编译器没有为它们生成数组赋值的信息,并且出于兼容性原因需要指针赋值。为声明的数组引入数组赋值会引入错误,虽然是虚假的分配(是一个= 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的原始内存的memcpy,因为这不能破坏现有的代码,所以它很容易被引用。作为一种意想不到的副作用,这隐含地引入了某种类型的数组赋值,但是这种情况在结构中的某处发生,所以这不能真正引入数组使用方式的问题。


28
投票

关于赋值运算符,C ++标准如下(C ++03§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对象的赋值,左右操作数必须具有相同的类型,因此右操作数的值只是复制到左操作数中。


2
投票

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

数组的两个基本问题是

  • 数组不知道自己的大小
  • 数组的名称在最轻微的挑衅时转换为指向其第一个元素的指针

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

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

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


0
投票

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

NUM2 = NUM​​1;

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

当然,struts2 = struts 1;

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


0
投票

没有进一步努力在C中加强数组的另一个原因可能是数组赋值不会那么有用。即使它可以通过将其包装在结构中而轻松实现(并且结构的地址可以简单地转换为数组的地址,甚至是数组的第一个元素的地址以进行进一步处理),但很少使用此功能。一个原因是不同大小的数组是不兼容的,这限制了赋值的好处,或相关的,通过值传递给函数。

对于数组为第一类类型的语言,大多数具有数组参数的函数都是针对任意大小的数组编写的。然后,该函数通常迭代给定数量的元素,即数组提供的信息。 (在C语言中,成语当然是传递指针和单独的元素计数。)不需要经常接受只有一个特定大小的数组的函数,因此不会遗漏多少。 (当您可以将其留给编译器为任何正在发生的数组大小生成单独的函数时,这会发生变化,就像使用C ++模板一样;这就是std::array有用的原因。)

© www.soinside.com 2019 - 2024. All rights reserved.