инициализации и присвоения

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

инициализации и присвоения

Непрочитанное сообщение Olej » 24 дек 2014, 02:43

Мне тут задали такой вопрос ... дословно:

Код: Выделить всё

[19:25:16] вот сишный код:
[19:25:31] : 

  int Static[5];
 
  int func(void)
  {
    int Stack[5];
 
    Static[5] = 0; 
    Stack [5] = 0; 
 
    return 0;
  }
[19:25:55] : например я перепишу это на джаву
[19:26:11] : это не вызовет ошибку времени компиляции
[19:26:19] : но это вызовет ошибку времени выполнения
[19:26:38] : я пытаюсь обратиться к пятому элементу массива размерностью 5
[19:26:48] : а элементы в нем: 0 1 2 3 4
[19:27:00] : что будет в С
[19:27:12] : скомпилируется ли это? если да - что будет при работе?
Хороший вопрос, для понимания того, что происходит... и на разных языках.
На конкретный прямой вопрос (там их, собственно, 2) краткий ответ будет такой:
- конечно это успешно скомпилируется ...
- а при работе такого кода ... может быть всё, что угодно
- конечно, запись за пределами массива - критическая ошибка, но эта ошибка может быть даже не замечена при выполнении.

А для подробного ответа лучше посмотреть код:

Код: Выделить всё

#include <stdio.h>
#include <stdlib.h>

//#define SIZE 3
//#define SIZE 5
#define SIZE 8

int Static0[ SIZE ] = {}, Static1[ SIZE ] = {}, Static2[ SIZE ] = {};

void show( int arr[], int* base ) {
   printf( "%p\t[%c%02d] : ", 
           &arr[ 0 ],
           ( &arr[ 0 ] - base < 0 ? '-' : '+' ),
           abs( &arr[ 0 ] - base ) );
   int i;
   for( i = 0; i < SIZE; i++ ) 
      printf( "%d%s", arr[ i ], ( SIZE - 1 != i ? ", " : "\n" ) );
}

void show3( int arr0[], int arr1[], int arr2[] ) {
   int *b = &arr0[ 0 ];
   show( arr0, b ), show( arr1, b ), show( arr2, b );   
   printf( "------------------------------------------\n" );
}

int main( int argc, char* argv[] ) {
   int Stack0[ SIZE ], Stack1[ SIZE ], Stack2[ SIZE ], i;

   show3( Stack0, Stack1, Stack2 );
   for( i = 0; i < SIZE; i++ )
      Stack0[ i ] = Stack1[ i ] = Stack2[ i ] = 0;
   show3( Stack0, Stack1, Stack2 );
   Stack1[ SIZE ] = 7;
   show3( Stack0, Stack1, Stack2 );

   show3( Static0, Static1, Static2 );
   Static1[ SIZE ] = 7;
   show3( Static0, Static1, Static2 );

   Static1[ -25 ] = 7;

   return 0;
}
Выполняем:

Код: Выделить всё

[Olej@modules Init]$ make init
gcc -Wall     init.c   -o init
[Olej@modules Init]$ ./init 
0x7fff3635d900	[+00] : 194, 0, 0, 0, 4196224, 0, 4195472, 0
0x7fff3635d8e0	[-08] : -1906175640, 48, 0, 0, 1, 0, 4196301, 0
0x7fff3635d8c0	[-16] : -1, 0, 1, 0, -1904148032, 48, -924658088, 32623
------------------------------------------
0x7fff3635d900	[+00] : 0, 0, 0, 0, 0, 0, 0, 0
0x7fff3635d8e0	[-08] : 0, 0, 0, 0, 0, 0, 0, 0
0x7fff3635d8c0	[-16] : 0, 0, 0, 0, 0, 0, 0, 0
------------------------------------------
0x7fff3635d900	[+00] : 7, 0, 0, 0, 0, 0, 0, 0
0x7fff3635d8e0	[-08] : 0, 0, 0, 0, 0, 0, 0, 0
0x7fff3635d8c0	[-16] : 0, 0, 0, 0, 0, 0, 0, 0
------------------------------------------
0x601060	[+00] : 0, 0, 0, 0, 0, 0, 0, 0
0x601080	[+08] : 0, 0, 0, 0, 0, 0, 0, 0
0x6010a0	[+16] : 0, 0, 0, 0, 0, 0, 0, 0
------------------------------------------
0x601060	[+00] : 0, 0, 0, 0, 0, 0, 0, 0
0x601080	[+08] : 0, 0, 0, 0, 0, 0, 0, 0
0x6010a0	[+16] : 7, 0, 0, 0, 0, 0, 0, 0
------------------------------------------
Ошибка сегментирования (core dumped)
Вы можете детально проанализировать результаты, а укрупнённо они:
- локально объявленные но не инициализированные переменные (массивы с том числе) - инициализированы начально мусором;
- глобально объявленные или статические переменные инициализированы нулевыми значениями (как мы легко увидим, они размещаются в совсем другой секции объектного файла);
- мы пишем (значение 7) за пределами массива №1, но никаких segmentation fault не получаем, мы "попадаем" (портим значения!) либо массива объявленного раньше для локальных переменных, либо позже - для глобальных.
- то же, что для массивов (здесь просто это чаще) может быть и для простых скалярных переменных:

Код: Выделить всё

int x = 2, *p = &x;
*(p + 10 ) = 3; // ;-)
- и только когда вы "вылезете" в адресе за пределы размещённой секции объектного кода - вот тогда получаете SIGSEGV! (последняя строчка кода, завершающая пример).

Размещение секций кода можем смотреть по собранному бинарному файлу:

Код: Выделить всё

[Olej@modules Init]$ nm init 
000000000060103c B __bss_start
0000000000601040 b completed.6338
0000000000601038 D __data_start
0000000000601038 W data_start
00000000004004c0 t deregister_tm_clones
0000000000400530 t __do_global_dtors_aux
0000000000600e18 t __do_global_dtors_aux_fini_array_entry
0000000000400808 R __dso_handle
0000000000600e28 d _DYNAMIC
000000000060103c D _edata
00000000006010c0 B _end
00000000004007f4 T _fini
0000000000400550 t frame_dummy
0000000000600e10 t __frame_dummy_init_array_entry
00000000004009d0 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000400418 T _init
0000000000600e18 t __init_array_end
0000000000600e10 t __init_array_start
0000000000400800 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000600e20 d __JCR_END__
0000000000600e20 d __JCR_LIST__
                 w _Jv_RegisterClasses
00000000004007f0 T __libc_csu_fini
0000000000400780 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
000000000040068f T main
                 U printf@@GLIBC_2.2.5
                 U puts@@GLIBC_2.2.5
00000000004004f0 t register_tm_clones
0000000000400580 T show
000000000040062e T show3
0000000000400490 T _start
0000000000601060 B Static0
0000000000601080 B Static1
00000000006010a0 B Static2
0000000000601040 D __TMC_END__
Это любопытно сопоставить с цифрами в полученном результате прогона.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: инициализации и присвоения

Непрочитанное сообщение Olej » 24 дек 2014, 02:56

Olej писал(а): Выполняем:
А ещё смешнее :lol: это выглядит для размерностей массива 3 или 5:

Код: Выделить всё

//#define SIZE 3
//#define SIZE 5
#define SIZE 8

Код: Выделить всё

[Olej@modules Init]$ make init
gcc -Wall     init.c   -o init
[Olej@modules Init]$ ./init
0x7fff0d505410  [+00] : 223368448, 32767, 0
0x7fff0d505400  [-04] : 4196224, 0, 4195472
0x7fff0d5053f0  [-08] : 194, 0, 0
------------------------------------------
0x7fff0d505410  [+00] : 0, 0, 0
0x7fff0d505400  [-04] : 0, 0, 0
0x7fff0d5053f0  [-08] : 0, 0, 0
------------------------------------------
0x7fff0d505410  [+00] : 0, 0, 0
0x7fff0d505400  [-04] : 0, 0, 0
0x7fff0d5053f0  [-08] : 0, 0, 0
------------------------------------------
0x601058        [+00] : 0, 0, 0
0x601040        [-06] : 0, 0, 0
0x60104c        [-03] : 0, 0, 0
------------------------------------------
0x601058        [+00] : 0, 0, 0
0x601040        [-06] : 0, 0, 0
0x60104c        [-03] : 7, 0, 0
------------------------------------------
Ошибка сегментирования (core dumped)

Код: Выделить всё

[Olej@modules Init]$ make init
gcc -Wall     init.c   -o init
[Olej@modules Init]$ ./init
0x7fffda8e4f60  [+00] : 4196224, 0, 4195472, 0, -628207520
0x7fffda8e4f40  [-08] : 1, 0, 4196301, 0, 194
0x7fffda8e4f20  [-16] : -1904148032, 48, 202991192, 32528, -1906175640
------------------------------------------
0x7fffda8e4f60  [+00] : 0, 0, 0, 0, 0
0x7fffda8e4f40  [-08] : 0, 0, 0, 0, 0
0x7fffda8e4f20  [-16] : 0, 0, 0, 0, 0
------------------------------------------
0x7fffda8e4f60  [+00] : 0, 0, 0, 0, 0
0x7fffda8e4f40  [-08] : 0, 0, 0, 0, 0
0x7fffda8e4f20  [-16] : 0, 0, 0, 0, 0
------------------------------------------
0x601050        [+00] : 0, 0, 0, 0, 0
0x601070        [+08] : 0, 0, 0, 0, 0
0x601090        [+16] : 0, 0, 0, 0, 0
------------------------------------------
0x601050        [+00] : 0, 0, 0, 0, 0
0x601070        [+08] : 0, 0, 0, 0, 0
0x601090        [+16] : 0, 0, 0, 0, 0
------------------------------------------
За счёт выравнивания границ начала массивов, которое делает компилятор, вы можете вообще писать свои данные даже не в соседний массив (или переменные), а в пустые промежутки между их размещениями, т.е. вообще в недоступное пространство.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: инициализации и присвоения

Непрочитанное сообщение Olej » 24 дек 2014, 03:03

Olej писал(а):А ещё смешнее :lol: это выглядит для размерностей массива 3 или 5:
А теперь для полноты картины то же самое, но C++ ... а то у некоторых бывают мнения, что ... "C++ - это круто" ;-).
Я для простоты и чистоты эксперимента просто скопирую тот же код, но скомпилирую его C++:

Код: Выделить всё

[Olej@modules Init]$ cp init.c  init1.cc 
[Olej@modules Init]$ make init1
g++ -Wall     init1.cc   -o init1
[Olej@modules Init]$ ./init 
0x7fffa15c65b0	[+00] : 194, 0, 0, 0, 4196224, 0, 4195472, 0
0x7fffa15c6590	[-08] : -1906175640, 48, 0, 0, 1, 0, 4196301, 0
0x7fffa15c6570	[-16] : -1, 0, 1, 0, -1904148032, 48, -393669032, 32536
------------------------------------------
0x7fffa15c65b0	[+00] : 0, 0, 0, 0, 0, 0, 0, 0
0x7fffa15c6590	[-08] : 0, 0, 0, 0, 0, 0, 0, 0
0x7fffa15c6570	[-16] : 0, 0, 0, 0, 0, 0, 0, 0
------------------------------------------
0x7fffa15c65b0	[+00] : 7, 0, 0, 0, 0, 0, 0, 0
0x7fffa15c6590	[-08] : 0, 0, 0, 0, 0, 0, 0, 0
0x7fffa15c6570	[-16] : 0, 0, 0, 0, 0, 0, 0, 0
------------------------------------------
0x601060	[+00] : 0, 0, 0, 0, 0, 0, 0, 0
0x601080	[+08] : 0, 0, 0, 0, 0, 0, 0, 0
0x6010a0	[+16] : 0, 0, 0, 0, 0, 0, 0, 0
------------------------------------------
0x601060	[+00] : 0, 0, 0, 0, 0, 0, 0, 0
0x601080	[+08] : 0, 0, 0, 0, 0, 0, 0, 0
0x6010a0	[+16] : 7, 0, 0, 0, 0, 0, 0, 0
------------------------------------------
Ошибка сегментирования (core dumped)
Как видите, результат абсолютно тот же!
Что лишний раз нас подтверждает в мысли, что C++ вовсе не отдельный новый язык, а надмножество языка C (что неоднократно утверждает и Б.Страуструп).

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: инициализации и присвоения

Непрочитанное сообщение Olej » 24 дек 2014, 04:14

Конечно, в Java, как было замечено, такой контроль будет присутствовать. Вот это уж совсем просто проиллюстрировать:

Код: Выделить всё

public class init {

   static int size = 8;

   static void show ( int arr[] ) {
      for( int i = 0; i < arr.length; i++ )   // цикл по всему массиву
         System.out.print( arr[ i ] + ", " );
      System.out.println();
   }

   public static void main( String[] args ) {
      int a[] = new int[ size ];              // создаём массив
      show( a );
      a[ size ] = size;
   }

}

Код: Выделить всё

[Olej@modules Init]$ javac init.java
[Olej@modules Init]$ java init
0, 0, 0, 0, 0, 0, 0, 0,
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 8
    at init.main(init.java:14)
ABRT problem creation: 'success'

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: инициализации и присвоения

Непрочитанное сообщение Olej » 24 дек 2014, 04:28

Olej писал(а):Конечно, в Java, как было замечено, такой контроль будет присутствовать.
Можно такой контроль сделать ... скажем в C++?
Конечно можно ... только осторожно :lol: - например с STL:

Код: Выделить всё

#include <iostream>
#include <iomanip>
#include <vector>
#include <cmath>
using namespace std;

//const int size = 3;
//const int size = 5;
const int size = 8;

static vector<int> Static0( size ), Static1( size ), Static2( size );

void show( vector<int>& arr, long base ) {
   long shift = (long)&arr - base;
   cout << &arr << "\t["<< ( shift < 0 ? '-' : '+' )
        << setw( 2 ) << setfill( '0' ) << abs( shift ) << "] {" 
        << arr.size() << "} ";
   for( vector<int>::iterator i = arr.begin(); i != arr.end(); i++ )
      cout << *i << ", ";
   cout << endl;
}

void show3( vector<int>& arr0, vector<int>& arr1, vector<int>& arr2 ) {
   long b = (long)&arr0;
   show( arr0, b ), show( arr1, b ), show( arr2, b );   
   cout << "------------------------------------------" << endl;
}

int main( int argc, char* argv[] ) {
   vector<int> Stack0( size ), Stack1( size ), Stack2( size );

   show3( Stack0, Stack1, Stack2 );
   Stack1[ size ] = 7;
   Stack1[ size * 2 ] = 8;
   show3( Stack0, Stack1, Stack2 );

   show3( Static0, Static1, Static2 );
   Static1[ size ] = 7;
   Static1[ size * 2 ] = 8;
   show3( Static0, Static1, Static2 );

   Static1.at( size ) = 7;   

   return 0;
}

Код: Выделить всё

[Olej@modules Init]$ make init2
g++ -Wall     init2.cc   -o init2
[Olej@modules Init]$ ./init2
0x7fff238cac20	[+00] {8} 0, 0, 0, 0, 0, 0, 0, 0, 
0x7fff238cac00	[-32] {8} 0, 0, 0, 0, 0, 0, 0, 0, 
0x7fff238cabe0	[-64] {8} 0, 0, 0, 0, 0, 0, 0, 0, 
------------------------------------------
0x7fff238cac20	[+00] {8} 0, 0, 0, 0, 0, 0, 0, 0, 
0x7fff238cac00	[-32] {8} 0, 0, 0, 0, 0, 0, 0, 0, 
0x7fff238cabe0	[-64] {8} 0, 0, 0, 0, 8, 0, 0, 0, 
------------------------------------------
0x603210	[+00] {8} 0, 0, 0, 0, 0, 0, 0, 0, 
0x603230	[+32] {8} 0, 0, 0, 0, 0, 0, 0, 0, 
0x603250	[+64] {8} 0, 0, 0, 0, 0, 0, 0, 0, 
------------------------------------------
0x603210	[+00] {8} 0, 0, 0, 0, 0, 0, 0, 0, 
0x603230	[+32] {8} 0, 0, 0, 0, 0, 0, 0, 0, 
0x603250	[+64] {8} 0, 0, 0, 0, 8, 0, 0, 0, 
------------------------------------------
terminate called after throwing an instance of 'std::out_of_range'
  what():  vector::_M_range_check
Аварийный останов (core dumped)
Обратите внимание, как индексация вектора [] записывает мусор в соседние переменные (как и с массивом), но обращение at() - возбуждает исключением при превышении размера.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: инициализации и присвоения

Непрочитанное сообщение Olej » 24 дек 2014, 04:30

Olej писал(а): Можно такой контроль сделать ... скажем в C++?
Конечно можно ... только осторожно :lol: - например с STL:
Но более того, и в C м в C++ можно просто указать опциями компилятора GCC, чтобы он добавлял код контроля размерности массивов. Попадётся опция на глаза - я впишу сюда ;-) .

Вопрос то в том: всегда ли такой контроль - это хорошо?

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 5 гостей