что компилятор делает с [i], который является массивом А что если указатель? - PullRequest
14 голосов
/ 15 января 2010

Мне сказали c-faq , что компилятор делает разные вещи для работы с [i], в то время как a является массивом или указателем. Вот пример из c-faq:

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

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

Но мне сказали, что при работе с [i] компилятор стремится преобразовать (который является массивом) в указатель на массив. Поэтому я хочу увидеть ассемблерные коды, чтобы узнать, какой из них правильный.

EDIT:

Вот источник этого утверждения. с-чаво И обратите внимание на это предложение:

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

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

Вот мой код:

// array.cpp
#include <cstdio>
using namespace std;

int main()
{
    char a[6] = "hello";
    char *p = "world";
    printf("%c\n", a[3]);
    printf("%c\n", p[3]);
}

А вот часть кода сборки, который я получил с помощью g ++ -S array.cpp

    .file   "array.cpp" 
    .section    .rodata
.LC0:
    .string "world"
.LC1:
    .string "%c\n"
    .text
.globl main
    .type   main, @function
main:
.LFB2:
    leal    4(%esp), %ecx
.LCFI0:
    andl    $-16, %esp
    pushl   -4(%ecx)
.LCFI1:
    pushl   %ebp
.LCFI2:
    movl    %esp, %ebp
.LCFI3:
    pushl   %ecx
.LCFI4:
    subl    $36, %esp
.LCFI5:
    movl    $1819043176, -14(%ebp)
    movw    $111, -10(%ebp)
    movl    $.LC0, -8(%ebp)
    movzbl  -11(%ebp), %eax
    movsbl  %al,%eax
    movl    %eax, 4(%esp)
    movl    $.LC1, (%esp)
    call    printf
    movl    -8(%ebp), %eax
    addl    $3, %eax
    movzbl  (%eax), %eax
    movsbl  %al,%eax
    movl    %eax, 4(%esp)
    movl    $.LC1, (%esp)
    call    printf
    movl    $0, %eax
    addl    $36, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret 

Не могу разобраться в механизме a [3] и p [3] из приведенных выше кодов. Такие как:

  • где было инициализировано "привет"?
  • что означает 1819043176 $? может быть это адрес памяти "привет" (адрес а)?
  • Я уверен, что "-11 (% ebp)" означает [3], но почему?
  • В "movl -8 (% ebp),% eax" содержание poniter p хранится в EAX, верно? Таким образом, $ .LC0 означает содержимое указателя p?
  • Что означает "movsbl% al,% eax"?
  • И обратите внимание на следующие 3 строки кодов:
    movl $ 1819043176, -14 (% ebp)
    movw $ 111, -10 (% ebp)
    movl $ .LC0, -8 (% ebp)

    Последний использует «movl», но почему он не переписал содержимое -10 (% ebp)? (Теперь я знаю ансер :), адрес является инкрементным и "movl $ .LC0 -8 (% ebp) будет перезаписывать только {-8, -7, -6, -5} (% ebp))

Извините, но я полностью запутался в механизме и коде сборки ...

Большое спасибо за вашу помощь.

Ответы [ 4 ]

5 голосов
/ 15 января 2010

a - указатель на массив символов. p - указатель на символ, который в данном случае указывает на строковый литерал.

movl    $1819043176, -14(%ebp)
movw    $111, -10(%ebp)

Инициализирует локальное «привет» в стеке (поэтому на него ссылаются через ebp). Поскольку в "hello" больше 4 байтов, требуется две инструкции.

movzbl  -11(%ebp), %eax
movsbl  %al,%eax

Ссылки a[3]: двухэтапный процесс из-за ограничения доступа к памяти, на которую ссылаются, хотя ebp (мой x86-fu немного ржавый).

movl -8(%ebp), %eax действительно ссылается на указатель p.

LC0 ссылается на «относительную память»: фиксированная ячейка памяти будет выделена после загрузки программы в память.

movsbl %al,%eax означает: «сдвинуться на один байт, опустить» (дай или возьми ... Я должен был бы посмотреть это ... Я немного заржавел на этом фронте). al представляет байт из регистра eax.

4 голосов
/ 15 января 2010

Обратимся к языковой стороне, поскольку ассемблерная сторона уже обработана:

Обратите внимание на это предложение: «выражение в форме a [i] заставляет массив распадаться на указатель, следуя приведенному выше правилу, а затем подписываться так же, как переменная указателя в выражении p [i] (хотя возможный доступ к памяти будет другим, «я довольно запутался в этом: поскольку a распался на указатель, то почему он имеет в виду« доступ к памяти будет другим? »

Это потому, что после затухания, доступ равен (теперь значение указателя) и указателю. Но разница в как в том, что значение указателя получается в первую очередь. Давайте посмотрим на пример:

char c[1];

char cc;
char *pc = &cc;

Теперь у вас есть массив. Этот массив не занимает хранения, кроме одного символа! для него не сохранен указатель . И у вас есть указатель, который указывает на символ. Указатель принимает размер одного адреса, и у вас есть один символ, на который указывает указатель. Теперь давайте посмотрим, что происходит в случае массива, чтобы получить значение указателя:

c[0] = 'A';
// #1: equivalent: *(c + 0) = 'A';
// #2: => 'c' appears not in address-of or sizeof 
// #3: => get address of "c": This is the pointer value P1

Случай с указателем другой:

pc[0] = 'A';
// #1: equivalent: *(pc + 0) = 'A';
// #2: => pointer value is stored in 'pc'
// #3: => thus: read address stored in 'pc': This is the pointer value P1

Как вы видите, для случая массива для получения значения указателя, где мы добавляем значение индекса (в данном случае скучно 0), нам не нужно читать из памяти, потому что адрес массив уже является необходимым значением указателя. Но для случая с указателем нужное нам значение указателя хранится в указателе: нам нужно одно чтение из памяти, чтобы получить этот адрес.

После этого путь для обоих равен:

// #4: add "0 * sizeof(char)" to P1. This is the address P2
// #5: store 'A' to address P2

Вот код ассемблера, сгенерированный для массива и регистр указателя:

        add     $2, $0, 65  ; write 65 into r2
        stb     $2, $0, c   ; store r2 into address of c
# pointer case follows
        ldw     $3, $0, pc  ; load value stored in pc into r3
        add     $2, $0, 65  ; write 65 into r2
        stb     $2, $3, 0   ; store r2 into address loaded to r3

Мы можем просто сохранить 65 (ASCII для 'A') по адресу c (который будет известен уже во время компиляции или соединения, когда он глобален). В случае указателя нам сначала нужно будет загрузить сохраненный им адрес в регистр 3, а затем записать 65 по этому адресу.

2 голосов
/ 15 января 2010

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

Как в массивах, так и в указателях, когда разыменовывается оператором [], компилятор оценивает адрес элемента, к которому вы индексируете, умножая индекс на размер типа данных и добавляя его к адресу указателя или массив.

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

Кроме того, массивы могут быть размещены в стеке, а это невозможно для указателей (хотя указатели могут быть установлены на адреса в стеке, но это может выглядеть ужасно).

0 голосов
/ 29 октября 2013

Эти определения похожи, но на самом деле они совсем другие.

Предположим, что ваши массивы объявлены внутри функции:
void f()
{
    char a[] = "hello";
    char *p = "world";
}

В первом случае «a» распадается на константный указатель, который указывает на 6 символов в STACK. Во втором случае «p» - это неконстантный указатель, который указывает на 6 символов в области CONST (сегмент данных).

Правильно написать:

a[3] = 'L';

но

p[3] = 'L';

выглядит правильно, но приведет к нарушению памяти, поскольку массив символов находится не в стеке, а в разделе только для чтения.

Кроме того,

a++

недопустимо ('a' распадается на константный указатель, который является r-значением), но

p++

допустимо (p является значением l).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...