如何在C中“实现”数组?

时间:2018-05-21 14:00:50

标签: c arrays pointers gcc

数组特别是 指针。可以肯定的是, lvalues 似乎都包含(1维)虚拟内存中某个位置的(1维)坐标。但请考虑这个例子。

std::vector< int > data{ 5, 3, 8, 4, -1, 1, 11, 9, 6 };

auto chosen_iter = std::find( data.begin(), data.end(), -1 );

std::swap( *chosen_iter, *( data.end() - 1 ) );

std::partial_sort( data.begin(), chosen_iter, data.end() - 1 );

std::swap( *chosen_iter, *( data.end() - 1 ) );

std::sort( chosen_iter + 1, data.end() );

想到的实际差异是:

  1. 数组知道它们有多大(以字节为单位)(并且,通过扩展,它们知道它们有多少元素);指针不会(但#include <stdlib.h> #include <stdio.h> int main(){ char buffer0[4096]; char* buffer1 = malloc(4096); printf("lvalue %16p sizeof %lu\n", (void *) buffer0, sizeof(buffer0)); printf("lvalue %16p sizeof %lu\n", (void *) buffer1, sizeof(buffer1)); // Example output: lvalue 0x7ffcb70e8620 sizeof 4096 // Example output: lvalue 0x7a4420 sizeof 8 } 必须知道指针有多大,知道只给指针malloc()多少......!)
  2. 阵列是“垃圾收集”(不需要free()它们);指针必须手动释放(如果它们拥有非常重要的内存量,即通过free()
  3. 阵列中的“实时”阵列(高虚拟内存地址,至少在我的平台上);指针在堆中“活”(低虚拟内存地址)
  4. 传递给函数时,数组衰减为指针
  5. 阵列无法调整大小;指针可以
  6. 总的来说,数组似乎比指针更聪明(但功能更少)(他们知道它们有多大,它们有多少元素,并且它们具有自动内存管理功能)。

    问题

    1. 阵列如何“知道”它们有多大?这是如何实现的?
    2. 一般来说,如何用C语言实现数组? (编译器是这样做的,还是内核?

3 个答案:

答案 0 :(得分:5)

  

数组如何“知道”它们有多大?这是如何实现的?

编译器知道这一点。

  

一般来说,如何用C语言实现数组? (编译器是这样做的,还是内核?

编译器。

=============================================== ===========================

您需要关注的一点是,数组是类型。它是派生类型。

引用C11,章节§6.2.5/ P20,

  

数组类型描述了一个连续分配的非空对象集   特定的成员对象类型,称为元素类型。元素类型应为   指定数组类型时完成。数组类型的特点是它们的   元素类型和数组中元素的数量。 [...]

因此,就像编译器知道任何其他类型的 size 一样,它本身也知道数组类型的大小。

总大小是根据元素类型的大小乘以该数组中元素的数量计算的。

答案 1 :(得分:5)

  1. 数组的类型包含其大小(作为编译时常量)及其成员类型。因此,由于编译器知道所有变量的类型,因此可以将sizeof(the_array)计算为sizeof(array_type.element_type) * array_type.element_count

  2. 就内存分配等而言,它们只是像任何其他变量一样对待:

    如果声明数组类型的自动变量,则会将sizeof(the_array_type)个字节添加到堆栈帧的大小。因此,当输入函数时,堆栈指针的增加足以存储数组的内容,当函数退出时,它会减少相同的数量。

    如果声明一个具有静态持续时间的变量,则sizeof(the_array_type)将保留在二进制数据段中。

    同样,这与处理任何类型的所有变量的方式相同。重要的是简单地说一个数组包含它的元素,所以它的大小是它的内容的大小,而一个指针只指向它的元素,它的大小完全独立于它指向的内容。

    当用作sizeof之外的r表达式时,数组的名称只是编译为其地址(并键入为指针)。

      

    编译器是这样做的,还是内核?

    所有这些都是由编译器完成的。

答案 2 :(得分:3)

  

数组如何“知道”它们有多大?这是如何实现的?

数组知道它们有多大 - 没有与数组相关联的元数据来指示大小(或类型或其他任何内容)。 在转换期间,编译器知道数组有多大,并且当时处理依赖于该知识的任何事物(指针算术,sizeof操作等)。一旦生成了机器代码,数组就只是笨拙的内存块 - 没有办法通过查看数组对象本身来判断在运行时数组的大小(除了可变修改的类型,如变量) -length数组,T arr[N]; // for any type T 操作在翻译期间计算,而非运行时间。)

  

一般来说,如何用C语言实现数组? (编译器是这样做的,还是内核?

数组只不过是相同类型的连续对象序列。声明

     +---+
arr: |   | arr[0]
     +---+
     |   | arr[1]
     +---+
     |   | arr[2]
     +---+
      ...
     +---+ 
     |   | arr[N-1]
     +---+

你得到了

arr

没有arr[i]对象独立于数组元素本身,也没有任何元数据可以放在任何地方,包括大小,起始地址,类型或其他任何内容。

下标操作*(arr + i) 定义i - 给定数组的起始地址,偏移sizeof个元素(不是字节!< / em>)从该地址开始并取消引用结果。

数组不是指针是正确的 - 但是,除非它是&或一元x = arr[i];运算符的操作数,或者是用于在声明中初始化字符数组的字符串文字,数组类型的表达式将被转换(“衰减”)为指针类型的表达式,表达式的值将是数组的第一个元素的地址(同样,这是全部在翻译过程中完成,而不是在运行时完成

因此,当您编写类似arr的内容时,编译器会将表达式ap = &arr;转换为指针值,因此下标操作可以正常工作。

相反,当您编写arr时,编译器T *转换为指针类型。结果仍然与第一个元素的地址相同,但类型是不同的 - 而不是T (*)[N],类型是T,或“指向N的指针元素数组{{1}}“。