Подтвердить что ты не робот

Почему C имеет "поддельные массивы"?

Я читаю Справочник ненавистников Unix, а в главе 9 я чего-то не понимаю:

C тоже не имеет массивов. Он имеет нечто похожее на массив но на самом деле является указателем на расположение памяти.

Я не могу себе представить, как хранить массив в памяти, кроме использования указателей, чтобы индексировать ячейки памяти. Как C реализует "поддельные" массивы, так или иначе? Есть ли достоверность в этом заявлении?

4b9b3361

Ответ 1

Я думаю, что авторы указывают, что массивы C - это всего лишь тонкий шпон на арифметике указателя. Оператор индекса определяется просто как a[b] == *(a + b), поэтому вы можете легко сказать 5[a] вместо a[5] и делать другие ужасные вещи, такие как доступ к массиву за последним индексом.

Сравнивая с этим, "истинный массив" будет тем, который знает свой собственный размер, не позволяет делать арифметику указателей, получать доступ к последнему индексу без ошибок или получать доступ к его содержимому с использованием другого типа элемента. Другими словами, "истинный массив" представляет собой узкую абстракцию, которая не привязывает вас к одному представлению - например, это может быть связанный список.

PS. Чтобы избавить себя от некоторых неприятностей: у меня нет на это никакого мнения, я просто объясняю цитату из книги.

Ответ 2

Существует различие между массивами C и указателями, и это видно из вывода выражений sizeof(). Например:

void sample1(const char * ptr)
{
   /* s1 depends on pointer size of architecture */
   size_t s1 = sizeof(ptr); 
}
size_t sample2(const char arr[])
{
   /* s2 also depends on pointer size of architecture, because arr decays to pointer */
   size_t s2 = sizeof(arr); 
   return s2;
}
void sample3(void)
{
   const char arr[3];
   /* s3 = 3 * sizeof(char) = 3 */
   size_t s2 = sizeof(arr); 
}
void sample4(void)
{
   const char arr[3];
   /* s4 = output of sample2(arr) which... depends on pointer size of architecture, because arr decays to pointer */
   size_t s4 = sample2(arr); 
}

В частности, теги sample2 и sample4, вероятно, являются причиной того, что люди склонны связывать C-массивы с C-указателями, потому что на других языках вы можете просто передать массивы в качестве аргумента функции и заставить ее работать "точно так же" как это было в функции вызывающего абонента. Точно так же из-за того, как работает C, вы можете передавать указатели вместо массивов, и это "правильно", тогда как в других языках с более четким различием между массивами и указателями это не будет.

Вы также можете просмотреть вывод sizeof() как следствие семантики C pass-by-value (поскольку C массивы распадаются на указатели).

Кроме того, некоторые компиляторы также поддерживают этот синтаксис C:

void foo(const char arr[static 2])
{
   /* arr must be **at least** 2 elements in size, cannot pass NULL */
}

Ответ 3

Заявление, которое вы цитируете, фактически неверно. Массивы в C не являются указателями.

Идея реализации массивов в качестве указателей использовалась в языках B и BCPL (предки C), но она не сохранилась при переходе к C. В раннем возрасте C "обратная совместимость" с B и BCPL рассматривалась несколько важно, поэтому массивы C тесно подражают поведению массивов B и BCPL (т.е. массивы C легко "распадаются" на указатели). Тем не менее, массивы C не являются "указателями на расположение памяти".

Цитата книги полностью фиктивная. Это заблуждение довольно распространено среди новичков C. Но то, как ему удалось попасть в книгу, вне меня.

Ответ 4

Автор, вероятно, означает, что массивы ограничены способами, которые заставляют их чувствовать себя гражданами второго сорта с точки зрения программиста. Например, две функции, одна в порядке, другая - нет:

int finefunction() {
    int ret = 5;
    return ret;
}

int[] wtffunction() {
    int ret[1] = { 5 };
    return ret;
}

Вы можете обойти это немного, обернув массивы в structs, но это просто подчеркивает, что массивы разные, они не похожи на другие типы.

struct int1 {
    int a[1];
}

int[] finefunction2() {
    struct int1 ret = { { 5 } };
    return ret;
}

Другим эффектом этого является то, что вы не можете получить размер массива во время выполнения:

int my_sizeof(int a[]) {
    int size = sizeof(a);
    return size;
}

int main() {
    int arr[5];
    // prints 20 4, not 20 20 as it would if arrays were 1st class things
    printf("%d %d\n", sizeof(arr), my_sizeof(arr)); 
}

Другой способ сказать, что говорят авторы, в терминологии C (и С++), "array" означает нечто иное, чем на большинстве других языков.


Итак, ваш вопрос с заголовком, как будет храниться "истинный массив" в памяти. Ну, нет ни одного вида "истинного массива". Если вам нужны истинные массивы в C, у вас есть в основном два варианта:

  • Использовать calloc для выделения буфера и хранить указатель и количество элементов здесь

    struct intarrayref {
      size_t count;
      int *data;
    }
    

    Эта структура в основном ссылается на массив, и вы можете хорошо ее использовать для функций и т.д. Вам нужно будет написать функции для работы на нем, например, создать копию фактических данных.

  • Используйте гибкий элемент массива и выделите всю структуру с помощью одного calloc

    struct intarrayobject {
        size_t count;
        int data[];
    }
    

В этом случае вы выделяете как метаданные (count), так и пространство для данных массива за один раз, но цена такова, что вы больше не можете передавать эту структуру как значение, потому что это оставит за дополнительными данными. Вы должны передать указатель на эту структуру на функции и т.д. Поэтому вопрос о том, можно ли считать это "истинным массивом" или слегка улучшенным нормальным массивом C., это вопрос.

Ответ 5

Как и вся книга, это случай троллинга, в частности, тип троллинга, который включает в себя утверждение чего-то почти-истинного, но неправильного, чтобы вызвать гневные ответы о том, почему это неправильно. C, скорее всего, имеет фактические массивы/типы массивов, о чем свидетельствует способ использования типов указателей и массивов (и многомерных массивов).