Как типы массивов и указателей обрабатываются внутренне в компиляторах C?( int *a;против.int a[] инт а[];)

StackOverflow https://stackoverflow.com/questions/1372811

Вопрос

Мне нужен языковой юрист с авторитетными источниками.

Взгляните на следующую тестовую программу, которая чисто компилируется в gcc:

#include <stdio.h>


void foo(int *a) {
    a[98] = 0xFEADFACE;
}

void bar(int b[]) {
    *(b+498) = 0xFEADFACE;
}

int main(int argc, char **argv) {

int a[100], b[500], *a_p;

*(a+99) = 0xDEADBEEF;
*(b+499) = *(a+99);

foo(a);
bar(b);

printf("a[98] == %X\na[99] == %X\n", a[98], a[99]);
printf("b[498] == %X\nb[499] == %X\n", b[498], b[499]);

a_p = a+98;
*a_p = 0xDEADFACE;

printf("a[98] == %X\na[99] == %X\n", a[98], a[99]);

}

Это дает результат, которого я ожидаю:

anon@anon:~/study/test_code$ gcc arrayType.c -o arrayType
anon@anon:~/study/test_code$ ./arrayType 
a[98] == FEADFACE
a[99] == DEADBEEF
b[498] == FEADFACE
b[499] == DEADBEEF
a[98] == DEADFACE
a[99] == DEADBEEF

Являются ли a и b одного и того же типа?Является int *a обрабатывается как тот же тип , что и int a[] внутренне в компиляторе?

С практической точки зрения int a[100], b[500], *a_p, b_a[]; все они кажутся однотипными.Мне трудно поверить, что компилятор постоянно настройка эти типы в различных обстоятельствах в моем приведенном выше примере.Я счастлив быть доказанный неправильно.

Может ли кто-нибудь решить этот вопрос за меня окончательно и подробно ?

Это было полезно?

Решение

Являются ли a и b одного и того же типа?Обрабатывается ли int * a как тот же тип, что и int a[] внутри компилятора?

От в comp.lang.C Вопросы и ответы:

...всякий раз, когда массив появляется в выражении, компилятор неявно генерирует указатель на первый элемент массива, точно так же, как если бы программист написал &a[0].(Исключения составляют случаи, когда массив является операндом оператора sizeof или &, или является инициализатором строкового литерала для массива символов ...)

...Учитывая массив a и указатель p, выражение вида a[i] приводит к тому, что массив распадается на указатель, следуя приведенному выше правилу, а затем подписывается точно так же, как была бы переменная указателя в выражении p[i] (хотя возможные обращения к памяти будут другими ...

Учитывая заявления о

char a[] = "hello";
char *p = "world";

...когда компилятор видит выражение a[3], он выдает код для запуска в указанном местоположении a, переместите три мимо него и приведите туда персонажа.Когда он видит выражение p[3], он выдает код для запуска в указанном местоположении p, извлеките там значение указателя, добавьте три к указателю и, наконец, извлеките символ, на который указано. Другими словами, a[3] находится в трех местах от (начала) объекта с именем a, в то время как p[3] находится в трех местах от объекта, на который указывает p.

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

Другие советы

Одно из отличий - int a[x][y] и int **a не являются взаимозаменяемыми.

http://www.lysator.liu.se/c/c-faq/c-2.html

2.10:

Массив массивов (т.е.двумерный массив в C) распадается на указатель на массив, а не на указатель на указатель.

a и b оба являются массивами целых чисел. a[0] не является ячейкой памяти, содержащей адрес памяти, это ячейка памяти, содержащая инт.

Массивы и указатели не являются ни идентичными, ни взаимозаменяемыми.Массивы - это эквивалент к указателям если когда значение lvalue типа array-of-T, которое появляется в выражении, распадается (за тремя исключениями) на указатель на его первый элемент;тип результирующего указателя - pointer-to-T.Это становится ясно при просмотре выходных данных сборки для соответствующего кода.К вашему сведению, три исключения возникают, когда массив является операндом размереof или & или буквальный строковый инициализатор для массива символов.

Если бы вы могли представить себе это:

char a[] = "hello";
char *p = "world";

это привело бы к созданию структур данных, которые могли бы быть представлены следующим образом:

   +---+---+---+---+---+---+
a: | h | e | l | l | o |\0 |
   +---+---+---+---+---+---+

   +-----+     +---+---+---+---+---+---+
p: |  *======> | w | o | r | l | d |\0 |
   +-----+     +---+---+---+---+---+---+

и поймите, что ссылка, подобная x[3], выдает разный код в зависимости от того, является ли x указателем или массивом.a[3] для компилятора означает:начните с местоположения a и переместите три мимо него и извлеките символ там.p[3] означает перейти к местоположению p, разыменовать там значение, переместить три за него и получить там символ.

Из Стандарт языка C:

6.3.2.1.3 Except when it is the operand of the sizeof operator or the 
          unary & operator, or is a string literal used to initialize 
          an array, an expression that has type ‘‘array of type’’ is
          converted to an expression with type ‘‘pointer to type’’ that 
          points to the initial element of the array object and is not 
          an lvalue. If the array object has register storage class, the
          behavior is undefined.

Предположим, что используется следующий код:

#include <stdio.h>
#include <string.h>
int main(void)
{
  char foo[10] = {0};
  char *p = foo;
  foo[0] = 'b';
  *(foo + 1) = 'a';
  strcat(foo, "t");
  printf("foo = %s, &foo = %p, &p = %p, sizeof foo = %lu, sizeof p = %lu\n", 
    foo, &foo, &p, (unsigned long) sizeof foo, (unsigned long) sizeof p);
  return 0;
}

foo объявляется как массив char из 10 элементов, все элементы которого инициализированы значением 0.p объявляется как указатель на char и инициализируется для указания на foo.

В очереди

char *p = foo;

выражение foo имеет тип "массив символов из 10 элементов".;поскольку foo не является операндом ни sizeof, ни & и не является строковым литералом, используемым для инициализации массива, его тип неявно преобразуется в "указатель на символ" и устанавливается как указывающий на первый элемент массива.Это значение указателя копируется в p.

В строках

foo[0] = 'b';
*(foo + 1) = 'a';

выражение foo имеет тип "массив символов из 10 элементов".;поскольку foo не является операндом ни sizeof, ни & и не является строковым литералом, используемым для инициализации массива, его тип неявно преобразуется в "указатель на символ" и устанавливается как указывающий на первый элемент массива.Выражение подстрочного индекса интерпретируется как "`*(foo + 0)".

В очереди

strcat(foo, "t");

foo имеет тип "массив символов из 10 элементов", а строковый литерал "t" имеет тип "массив символов из 2 элементов".;поскольку ни один из них не является операндом ни sizeof, ни &, и хотя "t" является строковым литералом, он не используется для инициализации массива, оба неявно преобразуются в тип "указатель на char", а значения указателя передаются в strcat().

В очереди

  printf("foo = %s, &foo = %p, &p = %p, sizeof foo = %lu, sizeof p = %lu\n", 
    foo, &foo, &p, (unsigned long) sizeof foo, (unsigned long) sizeof p);

первый экземпляр foo преобразуется в указатель на char, как описано выше.Второй экземпляр foo является операндом оператора &, поэтому его тип не преобразуется в "указатель на char", а тип выражения "&foo" равен "указателю на 10-элементный массив char", или "char (*)[10]".Сравните это с типом выражения "&p", которое является "указателем на указатель на символ", или "char **".Третий экземпляр foo является операндом оператора sizeof, поэтому опять же его тип не преобразуется, и sizeof возвращает количество байт, выделенных для массива.Сравните это с результатом sizeof p, который возвращает количество байт, выделенных указателю.

Всякий раз, когда кто-либо говорит вам "массив - это просто указатель", они искажают раздел из приведенного выше стандарта.Массивы не являются указателями, а указатели не являются массивами;однако во многих случаях вы можете обрабатывать массив как будто это был указатель, и вы можете обращаться с указателем как будто это был целый массив."p" можно было бы заменить на "foo" в строках 6, 7 и 8.Однако они не являются взаимозаменяемыми в качестве операндов для sizeof или & .

Редактировать:кстати, в качестве параметров функции,

void foo(int *a);

и

void foo(int a[]);

эквивалентны."a[]" интерпретируется как "*а".Обратите внимание, что это Только верно для параметров функции.

Посмотри сюда:

2.2:Но я слышал, что char a[] был идентичен char * a.

http://www.lysator.liu.se/c/c-faq/c-2.html

Я согласен с ответом sepp2k и цитатой из часто задаваемых вопросов Марка Рушакова на comp.lang.c.Позвольте мне добавить некоторые важные различия между этими двумя объявлениями и общую ловушку.

  1. Когда вы определяете a в виде массива (в контексте, отличном от аргумента функции, что является частным случаем) вы не можете записать a = 0;или a++;потому что a не является значением lvalue (значение, которое может отображаться слева от оператора присваивания).

  2. Определение массива резервирует пространство, в то время как указатель этого не делает.Следовательно, sizeof(array) вернет объем памяти, необходимый для хранения всех элементов массива (например, 10 раз по четыре байта для массива из 10 целых чисел в 32-разрядной архитектуре), тогда как sizeof(pointer) вернет только пространство памяти, необходимое для хранения этого указателя (например, 8 байт в 64-разрядной архитектуре).

  3. Когда вы добавляете объявления указателя или массива, все определенно расходится.Например, int **a является указателем на указатель на целое число.Его можно использовать как двумерный массив (со строками разного размера), выделив массив указателей на строки и поместив каждый из них в память для хранения целых чисел.Для доступа a[2][3] компилятор извлечет указатель в a[2] а затем переместите три элемента за пределы местоположения, на которое он указывает, чтобы получить доступ к значению.Сравните это с b[10][20] который представляет собой массив из 10 элементов, каждый из которых представляет собой массив из 20 целых чисел.Для доступа b[2][3] компилятор сместит начало области памяти массива, умножив 2 на размер 20 целых чисел и добавив размер еще 3 целых чисел.

Наконец, рассмотрим эту ловушку.Если у вас есть в одном файле C

int a[10];

и в другом

extern int *a;
a[0] = 42;

файлы будут скомпилированы и связаны без ошибок, но код не будет выполнять то, что вы могли бы ожидать;вероятно, это приведет к сбою при присвоении нулевого указателя.Причина в том, что во втором файле a является указателем, значение которого является содержимым первого файла a[0], т. е.изначально 0.

В вашем примере есть два a и два b .

В качестве параметров

void foo(int *a) {
    a[98] = 0xFEADFACE;
}

void bar(int b[]) {
    *(b+498) = 0xFEADFACE;
}

a и b относятся к одному и тому же типу:указатель на int.

Как переменные

int *a;
int b[10];

они не принадлежат к одному и тому же времени.Первый - это указатель, второй - массив.

Поведение массива

Массив (переменный он или нет) преобразуется неявно в большинстве контекстов в указатель на его первый элемент.Два контекста в C, где это не выполняется, являются аргументом sizeof и аргументом &;в C ++ есть еще несколько, связанных со ссылочными параметрами и шаблонами.

Я написал, переменная или нет, потому что преобразование выполняется не только для переменных, некоторые примеры:

int foo[10][10];
int (*bar)[10];
  • foo представляет собой массив из 10 массивов по 10 целых чисел.В большинстве случаев это будет преобразовано в указатель на его первый элемент типа указатель на массив из 10 int.

  • foo[10] представляет собой массив из 10 int;В большинстве случаев это будет преобразовано в указатель на его первый элемент типа указатель на int.

  • *bar представляет собой массив из 10 int;В большинстве случаев это будет преобразовано в указатель на его первый элемент типа указатель на int.

Немного истории

В B, прямой предок C, эквивалент

int x[10];

повлияло на то, что в текущем C мы бы написали

int _x[10];
int *x = &_x;

т.е. он выделил память и инициализировал указатель на нее.Некоторые люди , похоже , ошибочно полагают , что это все еще верно в C.

В NB - когда C больше не был B, но еще не назывался C - было время когда был объявлен указатель

int x[];

но

int foo[10];

имел бы текущее значение.Настройка параметра функции - это остаток того времени.

Являются ли a и b одного и того же типа?

ДА.[Править:Я должен уточнить:Параметр a функции foo имеет тот же тип, что и параметр b функции bar.Оба являются указателями на int.Локальная переменная a в main имеет тот же тип, что и локальная переменная b в int.Оба представляют собой массивы целых чисел (ну, на самом деле они разного типа, потому что не имеют одинакового размера.Но оба являются массивами).]

Обрабатывается ли int * a как тот же тип, что и int a[] внутри компилятора?

Обычно нет.Исключением является случай, когда вы пишете foo bar[] в качестве параметра функции (как вы сделали здесь) он автоматически становится foo *bar.

Однако при объявлении переменных без параметров существует большая разница.

int * a; /* pointer to int. points nowhere in paticular right now */
int b[10]; /* array of int. Memory for 10 ints has been allocated on the stack */
foo(a); /* calls foo with parameter `int*` */
foo(b); /* also calls foo with parameter `int*` because here the name b basically
           is a pointer to the first elment of the array */

Нет, это не одно и то же!Один из них - это указатель на int, другой - массив из 100 целых чисел.Так что да, они одинаковые!

Хорошо, я попытаюсь объяснить эту глупость.

* a и a [100] в основном совпадают для того, что вы делаете.Но если мы подробно рассмотрим логику обработки памяти для компилятора, то мы скажем следующее:

  • *a компилятор, мне нужна память, но сколько, я скажу тебе позже, так что пока остынь!
  • a[100] компилятор, мне нужна память сейчас, и Я знаю, что мне нужно 100, так что убедитесь, что они у нас есть!

Оба являются указатели.И ваш код может обращаться с ними одинаково и топтать память рядом с этими указателями сколько угодно.Но, a[100] является ли непрерывная память из указателя выделенной во время компиляции, в то время как * a выделяет указатель только потому, что он не знает, когда вам понадобится память (кошмары с памятью во время выполнения).

Итак, Кого это Волнует, верно?Ну, некоторые функции, такие как sizeof() забота. sizeof(a) вернет другой ответ для *a и для a[100].И это тоже будет отличаться в функциях.В данном случае с функциями компилятор знает разницу, так что вы можете использовать это в своих интересах и в своем коде, например, для циклов, memcpy и т.д.Давай, попробуй.

Это огромный вопрос, но ответ, который я даю здесь, таков.Компилятор знает тонкую разницу, и он создаст код, который будет выглядеть одинаково в большинстве случаев, но по-другому, когда это имеет значение.Вам решать, что * a или a [100] означает для cimpiler и где он будет относиться к этому по-другому.Они могут быть фактически одинаковыми, но это не одно и то же.И что еще хуже, вы можете изменить всю игру, вызвав функцию, подобную вашей.

Фух...Стоит ли удивляться, что управляемый код, подобный c #, сейчас так популярен?!

Редактировать: Я должен также добавить, что вы можете сделать *a_p = X, но попробуйте сделать это с одним из ваших массивов!Массивы работают с памятью точно так же, как указатели, но их нельзя перемещать или изменять размер.Указатели , подобные *a_p может указывать на разные вещи.

Я брошу свою шляпу на ринг за простое объяснение этого:

  • Массив - это последовательность смежных хранилищ одного и того же типа

  • Указатель - это адрес одного хранилища

  • Взятие адреса массива дает адрес (т.е. указатель на) его первого элемента.

  • К элементам массива можно получить доступ через указатель на первый элемент массива.Это работает, потому что оператор подстрочного индекса [] определен для указателей определенным образом предназначен для облегчения этого.

  • Массив может быть передан там, где ожидается параметр указателя, и он будет автоматически преобразован в указатель на первый элемент (хотя это не рекурсивный для нескольких уровней указателей или многомерных массивов).Опять же, это сделано специально.

Таким образом, во многих случаях один и тот же фрагмент кода может работать с массивами и смежными блоками памяти, которые не были выделены как массив из-за намеренно особой связи между массивом и указателем на его первый элемент.Однако это разные типы, и они действительно ведут себя по-разному в некоторых обстоятельствах, напримеруказатель на массив - это совсем не то же самое, что указатель на указатель.

Вот недавний вопрос SO, который затрагивает проблему с указателем на массив по сравнению с указателем на указатель: В чем разница между "abc" и {"abc"} в C?

Если у вас есть указатель на массив символов (и вы хотите получить размер этого массива), вы не можете использовать sizeof(ptr), а вместо этого должны использовать strlen(ptr) + 1!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top