通过阅读C中有关指针和数组的一些细节,我有点困惑。 一方面,数组可以看作数据类型。另一方面,阵列往往是不可修改的左值。我想编译器会做一些事情,比如用常量地址替换数组的标识符,以及用于计算索引在运行时给出的位置的表达式。
myArray[3] -(compiler)-> AE8349F + 3 * sizeof(<type>)
当说数组是数据类型时,这究竟意味着什么? 我希望你能帮助我澄清我对数组实际是什么以及编译器如何处理它的困惑。
答案 0 :(得分:16)
当谈到数组是一种数据类型时,这究竟意味着什么?
数据类型是具有预定义特征的值的一组数据。数据类型的示例有:整数,浮点单元号,字符,字符串和指针
数组是一组内存位置,它们都具有相同的名称和相同的类型。
如果您想知道为什么数组不可修改那么我读过的最佳解释是;
丹尼斯·里奇(Dennis Ritchie)心中没有完全形成C;它来源于早期的B语言(源自BCPL)。 1 B是一种“无类型”语言;它没有用于整数,浮点数,文本,记录等的不同类型。相反,一切都只是一个固定长度的单词或“单元格”(本质上是一个无符号整数)。记忆被视为线性细胞阵列。在B中分配数组时,例如auto V[10];
编译器分配了11个单元;阵列本身的10个连续单元格,以及绑定到包含第一个单元格位置的V的单元格:
+----+
V: | | -----+
+----+ |
... |
+----+ |
| | <----+
+----+
| |
+----+
| |
+----+
| |
+----+
...
当Ritchie向C添加struct
类型时,他意识到这种安排给他带来了一些问题。例如,他想创建一个结构类型来表示文件或目录表中的条目:
struct {
int inumber;
char name[14];
};
他希望结构不仅以抽象的方式描述条目,而且还要表示实际文件表条目中的位,这些位没有额外的单元格或单词来存储第一个元素的位置。数组。所以他摆脱了它 - 而不是留出一个单独的位置来存储第一个元素的地址,他写了C,这样在计算数组表达式时将计算第一个元素的地址。
这就是为什么你不能做像
这样的事情int a[N], b[N];
a = b;
因为a
和b
在该上下文中评估指针值;它等同于写3 = 4
。内存中没有任何内容实际上存储数组中第一个元素的地址;编译器只是在翻译阶段计算它。
<小时/> <子> 1。这全部来自论文The Development of the C Language
有关详细信息,您可以阅读此answer。
编辑:为了更清晰;可修改的l值,不可修改的l值和amp;之间的差异。 r值(简称);
这些表达方式的区别在于:
- 可修改的l值是可寻址的(可以是一元&amp;的操作数)和assignable(可以是=的左操作数)。
- 不可修改的l值是可寻址的,但不可分配。
- r-value 既不可寻址也不可分配。
答案 1 :(得分:0)
数组是一个连续的内存块。这意味着它按顺序排列在内存中。假设我们定义了一个数组:
int x[4];
sizeof(int) == 32
位。
这将在这样的记忆中列出(选择一个任意的起始地址,让我们说0x00000001
)
0x00000001 - 0x00000004
[element 0]
0x00000005 - 0x00000008
[element 1]
0x00000009 - 0x0000000C
[element 2]
0x0000000D - 0x00000010
[element 3]
编译器替换标识符是正确的。记住(如果你已经学会了这个。如果没有,那么你正在学习新的东西!)数组本质上是一个指针。在C / C ++中,数组名称是指向数组的第一个元素的指针(或者在我们的示例中指向地址0x00000001
的指针)。通过这样做:
std::cout << x[2];
你告诉编译器将2添加到该内存地址,即指针算术。 让我们说你使用变量来索引:
int i = 2;
std::cout << x[i];
编译器看到了这个:
int i = 2;
std::cout << x + (i * sizeof(int));
它基本上将数据类型的大小乘以给定的索引,并将其添加到数组的基址。编译器基本上采用索引操作符[]
并使用指针将其转换为加法。
如果你真的想转过头来,请考虑以下代码:
std::cout << 2[x];
这完全有效。如果你能弄清楚原因,那么你就有了这个概念。