Указатель арифметики вокруг приведения

В настоящее время я обучаюсь в классе CS107, который делает следующие предположения:

  • sizeof(int) == 4
  • sizeof(short) == 2
  • sizeof(char) == 1
  • прямой порядок байтов

Мой профессор показал следующий код:

int arr[5];
((short*)(((char*) (&arr[1])) + 8))[3] = 100;

Вот 20 байтов, представляющих arr:

|....|....|....|....|....|

Мой профессор утверждает, что здесь &arr[1] указывает, с чем я согласен.

|....|....|....|....|....|
     x

Теперь я понимаю, что (char*) делает указатель шириной char (1 байт) вместо ширины int (4 байта).

Чего я не понимаю, так это + 8, на что, по словам моего профессора, указывает:

|....|....|....|....|....|
                         x

Но разве он не должен указывать здесь, поскольку он идет вперед в 8 раз больше размера char (1 байт)?

|....|....|....|....|....|
               x

person Alexey    schedule 17.02.2015    source источник
comment
Ты прав. Напишите код для вывода значений указателей, чтобы показать профессору, что вы правы.   -  person R Sahu    schedule 17.02.2015
comment
Все зависит от sizeof(int), что не обязательно 4.   -  person Blagovest Buyukliev    schedule 17.02.2015
comment
@BlagovestBuyukliev Мой профессор предположил, что sizeof(int) равно 4 на протяжении всего урока. Извините, я должен был упомянуть об этом.   -  person Alexey    schedule 17.02.2015
comment
Алексей - 1, профессор - 0.   -  person lurker    schedule 17.02.2015
comment
Вам лучше спросить профессора, является ли предполагаемая машина прямой или прямым порядком байтов.   -  person Weather Vane    schedule 17.02.2015
comment
@WeatherVane Извините, я пропустил еще одну вещь: это должен быть обратный порядок байтов.   -  person Alexey    schedule 17.02.2015
comment
В некоторой степени связано: это классический пример строгого нарушения псевдонима, поэтому этот код в любом случае не имеет четко определенной семантики.   -  person mafso    schedule 17.02.2015
comment
@mafso. Совершенно верно. Он был в порядке, пока не бросил на short*. Его заявление о прямом порядке байтов проясняет самые реалистичные вопросы о семантике, но это все еще неопределенное поведение.   -  person Persixty    schedule 17.02.2015


Ответы (3)


Давайте рассмотрим это шаг за шагом. Ваше выражение можно разложить следующим образом:

((short*)(((char*) (&arr[1])) + 8))[3]
-----------------------------------------------------
char *base = (char *) &arr[1];
char *base_plus_offset = base + 8;
short *cast_into_short = (short *) base_plus_offset;
cast_into_short[3] = 100;

base_plus_offset указывает на положение байта 12 в массиве. cast_into_short[3] относится к значению short в местоположении 12 + sizeof(short) * 3, которое в вашем случае равно 18.

person Blagovest Buyukliev    schedule 17.02.2015
comment
И поэтому такой код не следует писать в одну строку. Здесь кристально ясно, что происходит. Я полагаю, что в коде вопроса профессор запутался, поставив слишком много скобок. - person gnasher729; 17.02.2015
comment
@ gnasher729: Или, по крайней мере, избегайте совершенно бесполезных скобок типа (char *)(&arr[1]) (который, кажется, здесь для людей, не знающих, что приведение может принимать унарное выражение, что, если бы это было запрещено, все равно не компилировалось бы без паренсов; сомневаюсь, что это проясняет, даже не может быть выражено в терминах старшинства). Хотя я согласен с тем, что в этом случае, наверное, следует разделить. - person mafso; 17.02.2015
comment
@mafso: Вторая пара круглых скобок также бесполезна, поэтому версия этого выражения, не относящаяся к культу карго, будет: ((short *) ((char *) &arr[1] + 8))[3]. - person Blagovest Buyukliev; 17.02.2015

Выражение установит два байта через 18 байтов после начала arr в значение 100.

#include <stdio.h>

int main() {

    int arr[5];

    char* start=(char*)&arr;
    char* end=(char*)&((short*)(((char*) (&arr[1])) + 8))[3];

    printf("sizeof(int)=%zu\n",sizeof(int));
    printf("sizeof(short)=%zu\n",sizeof(short));
    printf("offset=%td <- THIS IS THE ANSWER\n",(end-start));
    printf("100=%04x (hex)\n",100);

    for(size_t i=0;i<5;++i){

       printf("arr[%zu]=%d (%08x hex)\n",i,arr[i],arr[i]);

    }

}

Возможный выход:

sizeof(int)=4
sizeof(short)=2
offset=18 <- THIS IS THE ANSWER
100=0064 (hex)
arr[0]=0 (00000000 hex)
arr[1]=0 (00000000 hex)
arr[2]=0 (00000000 hex)
arr[3]=0 (00000000 hex)
arr[4]=6553600 (00640000 hex)

Во всех ваших махинациях профессора он сдвигал вам 1 целое число, 8 символов / байтов и 3 коротких строки, что 4 + 8 + 6 = 18 байтов. Бинго.

Обратите внимание, что этот вывод показывает, что машина, на которой я запускал это, имеет 4-байтовые целые числа, 2-байтовые короткие (обычные) и с прямым порядком байтов, потому что последние два байта массива были установлены на 0x64 и 0x00 соответственно.

Я нахожу ваши диаграммы ужасно запутанными, потому что они не очень понятны, если вы имеете в виду '|' быть адресами или нет.

|....|....|....|....|
012345678901234567890
    ^     1 ^     ^ 2
A   X       C     S B

Включите полоски ('|') A - начало Arr, а B - «один за концом» (юридическая концепция в C).

X - это адрес, на который указывает выражение & Arr [1]. C выражением (((char *) (& arr [1])) + 8). S всем выражением. S и следующий за ним байт назначаются, и то, что это означает, зависит от порядка байтов вашей платформы.

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

person Persixty    schedule 17.02.2015

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

#include <stdio.h>

int main( int argc, char* argv[] )
{
    int arr[5];
    int i;

    for( i = 0; i < 5; i++ )
        arr[i] = 0;

    printf( "Before: " );

    for( i = 0; i < sizeof(int)*5; i++ )
        printf( "%2.2X ", ((char*)arr)[i] );

    printf( "\n" );

    ((short*)(((char*) (&arr[1])) + 8))[3] = 100;

    printf( "After: " );

    for( i = 0; i < sizeof(int)*5; i++ )
        printf( "%2.2X ", ((char*)arr)[i] );
    printf( "\n" );

    return 0;
}

Начнем с самого внутреннего:

int указатель на (arr + 4)

&arr[1]
|...|...|...|...|...
    Xxxx

указатель char на (arr + 4)

(char*)(&arr[1])
|...|...|...|...|...
    X

указатель char на (arr + 4 + 8)

((char*)(&arr[1])) + 8)
|...|...|...|...|...
            X

короткий указатель на (arr + 4 + 8)

(short*)((char*)(&arr[1])) + 8)
|...|...|...|...|...
            Xx

short at (arr + 4 + 8 + (3 * 2)) (это индекс массива)

((short*)((char*)(&arr[1])) + 8))[3]
|...|...|...|...|...
                  Xx

То, какой именно байт здесь будет изменен, зависит от порядка байтов вашей системы. На моем little endian x86 я получаю следующий результат:

Before: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
After:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 00

Удачи с твоим курсом.

person Brian Sidebotham    schedule 17.02.2015
comment
Ваш анализ выражения верен, но вы, кажется, неправильно поняли утверждение ОП, когда вы сказали, что он не прав. Его (пока что одно) редактирование не имеет значения, поскольку добавленное приведение к short * не является частью подвыражения, о котором он спрашивает. - person John Bollinger; 17.02.2015
comment
@JohnBollinger Спасибо, я исправил это утверждение! Также добавьте положение указателя и использование памяти. - person Brian Sidebotham; 18.02.2015