В C, почему я могу видеть значение, записанное после конца массива в другой переменной?

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

Но, в конце концов, я узнал кое-что интересное для себя. Я не знаю, почему этот результат происходит до сих пор.

max_arr_count_index присваивается в зависимости от значения arr[5], которое находится за концом массива +1.

Есть ли кто-нибудь, кто может мне это объяснить? Я знаю, что этого не должно быть. Я присвоил значение последнему индексу массива (здесь arr[5] = 30 в проблемном случае), и это небезопасно, и это неопределенное поведение, как определено стандартом.

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

LLVM и GCC дали мне тот же результат.

Код и результат ниже:

[Случай без проблем: я не присваиваю значение после конца индекса]

#include <stdio.h>

int arr[] = {11,33,55,77,88};
int max_arr_count_index = (sizeof(arr) / sizeof(arr[0]));

// print all
void print_all_arr(int* arr)
{
    // just print all arr datas regarding index.
    for(int i = 0; i < max_arr_count_index; i++) {
        printf("arr[%d] = %d \n", i, arr[i]);
    }
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("[before]max_arr_count_index : %d\n", max_arr_count_index);
    printf("[before]The original array elements are :\n");
    print_all_arr(arr);
    arr[0] = 1;
    arr[1] = 2;
    arr[2] = 3;
    arr[3] = 4;
    arr[4] = 5;
    // arr[5] = 1000;
    printf("[after]max_arr_count_index : %d\n", max_arr_count_index);
    printf("[after]The array elements after :\n");

    print_all_arr(arr);

    return 0;
}

Результат без проблем приведен ниже:

[before]max_arr_count_index : 5
[before]The original array elements are :
arr[0] = 11 
arr[1] = 33 
arr[2] = 55 
arr[3] = 77 
arr[4] = 88 
[after]max_arr_count_index : 5
[after]The array elements after :
arr[0] = 1 
arr[1] = 2 
arr[2] = 3 
arr[3] = 4 
arr[4] = 5 
Program ended with exit code: 0

[Проблемный случай: я присвоил значение за концом индекса]

#include <stdio.h>

int arr[] = {11,33,55,77,88};
int max_arr_count_index = (sizeof(arr) / sizeof(arr[0]));

// print all
void print_all_arr(int* arr)
{
    // just print all arr datas regarding index.
    for(int i = 0; i < max_arr_count_index; i++) {
        printf("arr[%d] = %d \n", i, arr[i]);
    }
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("[before]max_arr_count_index : %d\n", max_arr_count_index);
    printf("[before]The original array elements are :\n");
    print_all_arr(arr);
    arr[0] = 1;
    arr[1] = 2;
    arr[2] = 3;
    arr[3] = 4;
    arr[4] = 5;

    /* Point is this one. 
       If I assign arr[5] 30, then, max_arr_count_index is changed also as            
       30. if I assign arr[5] 10000 max_arr_count_index is assigned 10000.
    */

    arr[5] = 30;

    /* Point is this one. 
       If I assign arr[5] 30, then, max_arr_count_index is changed also as            
       30. if I assign arr[5] 10000 max_arr_count_index is assigned 10000.
    */

    printf("[after]max_arr_count_index : %d\n", max_arr_count_index);
    printf("[after]The array elements after arr[5] is assigned 30 :\n");

    print_all_arr(arr);

    return 0;
}

Результат ниже:

[before]max_arr_count_index : 5
[before]The original array elements are :
arr[0] = 11 
arr[1] = 33 
arr[2] = 55 
arr[3] = 77 
arr[4] = 88 
[after]max_arr_count_index : 30
[after]The array elements after arr[5] is assigned 30 :
arr[0] = 1 
arr[1] = 2 
arr[2] = 3 
arr[3] = 4 
arr[4] = 5 
arr[5] = 30 
arr[6] = 0 
arr[7] = 0 
arr[8] = 0 
arr[9] = 0 
arr[10] = 0 
arr[11] = 0 
arr[12] = 0 
arr[13] = 0 
arr[14] = 0 
arr[15] = 0 
arr[16] = 0 
arr[17] = 0 
arr[18] = 0 
arr[19] = 0 
arr[20] = 0 
arr[21] = 0 
arr[22] = 0 
arr[23] = 0 
arr[24] = 0 
arr[25] = 0 
arr[26] = 0 
arr[27] = 0 
arr[28] = 0 
arr[29] = 0 
Program ended with exit code: 0

person boraseoksoon    schedule 04.11.2016    source источник
comment
как только вы забредете в земли неопределенного поведения, все может случиться   -  person AndersK    schedule 04.11.2016


Ответы (4)


Таким образом, очевидно, что с точки зрения стандарта C это неопределенное поведение, и компилятор может выпусти демонов из своего носа, и все будет в порядке.

Но вы хотите пойти глубже, так как вы просите «под капотом», поэтому нам, по сути, придется искать вывод ассемблера. Отрывок (созданный с участием gcc -g test test.c и objdump -S --disassemble test):

int main(int argc, const char * argv[]) {
 743:   55                      push   %rbp
 744:   48 89 e5                mov    %rsp,%rbp
 747:   48 83 ec 10             sub    $0x10,%rsp
 74b:   89 7d fc                mov    %edi,-0x4(%rbp)
 74e:   48 89 75 f0             mov    %rsi,-0x10(%rbp)
    // insert code here...
    printf("[before]max_arr_count_index : %d\n", max_arr_count_index);
 752:   8b 05 fc 08 20 00       mov    0x2008fc(%rip),%eax        # 201054 <max_arr_count_index>
 758:   89 c6                   mov    %eax,%esi
 75a:   48 8d 3d 37 01 00 00    lea    0x137(%rip),%rdi        # 898 <_IO_stdin_used+0x18>
 761:   b8 00 00 00 00          mov    $0x0,%eax
 766:   e8 35 fe ff ff          callq  5a0 <printf@plt>
    printf("[before]The original array elements are :\n");
 76b:   48 8d 3d 4e 01 00 00    lea    0x14e(%rip),%rdi        # 8c0 <_IO_stdin_used+0x40>
 772:   e8 19 fe ff ff          callq  590 <puts@plt>
    print_all_arr(arr);
 777:   48 8d 3d c2 08 20 00    lea    0x2008c2(%rip),%rdi        # 201040 <arr>
 77e:   e8 6d ff ff ff          callq  6f0 <print_all_arr>
    arr[0] = 1;
 783:   c7 05 b3 08 20 00 01    movl   $0x1,0x2008b3(%rip)        # 201040 <arr>
 78a:   00 00 00 
    arr[1] = 2;
 78d:   c7 05 ad 08 20 00 02    movl   $0x2,0x2008ad(%rip)        # 201044 <arr+0x4>
 794:   00 00 00 
    arr[2] = 3;
 797:   c7 05 a7 08 20 00 03    movl   $0x3,0x2008a7(%rip)        # 201048 <arr+0x8>
 79e:   00 00 00 
    arr[3] = 4;
 7a1:   c7 05 a1 08 20 00 04    movl   $0x4,0x2008a1(%rip)        # 20104c <arr+0xc>
 7a8:   00 00 00 
    arr[4] = 5;
 7ab:   c7 05 9b 08 20 00 05    movl   $0x5,0x20089b(%rip)        # 201050 <arr+0x10>
 7b2:   00 00 00 
    /* Point is this one. 
       If I assign arr[5] 30, then, max_arr_count_index is changed also as            
       30. if I assign arr[5] 10000 max_arr_count_index is assigned 10000.
    */

    arr[5] = 30;
 7b5:   c7 05 95 08 20 00 1e    movl   $0x1e,0x200895(%rip)        # 201054 <max_arr_count_index>
 7bc:   00 00 00 
    /* Point is this one. 
       If I assign arr[5] 30, then, max_arr_count_index is changed also as            
       30. if I assign arr[5] 10000 max_arr_count_index is assigned 10000.
    */

    printf("[after]max_arr_count_index : %d\n", max_arr_count_index);
 7bf:   8b 05 8f 08 20 00       mov    0x20088f(%rip),%eax        # 201054 <max_arr_count_index>
 7c5:   89 c6                   mov    %eax,%esi
 7c7:   48 8d 3d 22 01 00 00    lea    0x122(%rip),%rdi        # 8f0 <_IO_stdin_used+0x70>
 7ce:   b8 00 00 00 00          mov    $0x0,%eax
 7d3:   e8 c8 fd ff ff          callq  5a0 <printf@plt>
    printf("[after]The array elements after insertion :\n");
 7d8:   48 8d 3d 39 01 00 00    lea    0x139(%rip),%rdi        # 918 <_IO_stdin_used+0x98>
 7df:   e8 ac fd ff ff          callq  590 <puts@plt>

    print_all_arr(arr);
 7e4:   48 8d 3d 55 08 20 00    lea    0x200855(%rip),%rdi        # 201040 <arr>
 7eb:   e8 00 ff ff ff          callq  6f0 <print_all_arr>

    return 0;
 7f0:   b8 00 00 00 00          mov    $0x0,%eax
}

Как видите, даже на этом уровне дизассемблер уже знает, что вы фактически устанавливаете max_arr_count_index. Но почему?

Это потому, что структура памяти, созданная GCC, просто такая (и мы использовали -g с gcc, чтобы встроить отладочную информацию, чтобы дизассемблер мог знать, какая ячейка памяти является каким полем). У вас есть глобальный массив из пяти целых чисел и глобальная переменная типа int, объявленные сразу друг за другом. Глобальная переменная int просто находится сразу за массивом в памяти. Таким образом, доступ к целому числу сразу за концом массива дает max_arr_count_index.

Помните, что доступ к элементу i массива arr, например. ints (по крайней мере, на всех известных мне архитектурах) просто обращается к ячейке памяти arr+sizeof(int)*i, где arr — адрес первого элемента.

Как уже было сказано, это поведение undefined. GCC также может упорядочить глобальную переменную int перед массивом, что приведет к различным эффектам, возможно, даже к завершению программы при попытке доступа к arr[5], если в этом месте нет действительной страницы памяти.

person Jonas Schäfer    schedule 04.11.2016
comment
Ага. Изучение вывода ассемблера - хороший подход. Проголосовал. - person Bathsheba; 04.11.2016
comment
Спасибо за крутой подход к сборке. Это полностью объясняется этим. - person boraseoksoon; 04.11.2016

Доступ к массиву за пределами вызывает неопределенное поведение. Ничего хорошего в этом случае ждать не приходится. Размер arr равен 5. Вы можете получить доступ к arr с arr[0] по arr[4].

Если на мгновение отвлечься от УБ, объяснение поведения

/* Point is this one. 
   If I assign arr[5] 30, then, max_arr_count_index is changed also as            
   30. if I assign arr[5] 10000 max_arr_count_index is assigned 10000.
*/

может быть переменная max_arr_count_index объявлена ​​сразу после массива arr. Компилятор может выделить память для max_arr_count_index сразу за последним элементом массива arr. Например, если arr[4] находится в 0x100, то память для max_arr_count_index выделяется в 0x104. Таким образом, за массивом arr находится адрес 0x104. Поскольку &arr[5] — это тот же адрес, что и max_arr_count_index, присвоение значения arr[5] запишет это значение на адрес max_arr_count_index. Обратите внимание, что это не совсем то, что происходит. Это интуиция для такого поведения. Как только есть UB, все ставки сняты.

person haccks    schedule 04.11.2016

Вы объявили max_arr_count_index после arr, поэтому max_arr_count_index, вероятно, находится в самом следующем адресе относительно arr, поэтому присвоение arr+5 является присвоением max_arr_count_index адресу.

person Gabriel    schedule 04.11.2016

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

arr[i] оценивается как *(arr + i). Очевидно, что между arr и max_arr_count_index, вероятно, нет заполнения, поэтому arr + 5, вероятно, равно &max_arr_count_index. Но арифметика указателей допустима только в массивах. Вы можете установить указатель на единицу за концом массива, но поведение при разыменовывании будет неопределенным.

Конечно, в другой день компилятор может съесть вашу кошку.

person Bathsheba    schedule 04.11.2016
comment
Как этот вопрос является рецептом для отрицательного голосования? Я часто задаюсь вопросом, почему что-то ведет себя неправильно. Эти вопросы могут обеспечить более глубокое понимание того, как все работает под капотом. - person glglgl; 05.11.2016