Странное поведение глобальной переменной, после изменения имени проблема исчезает - PullRequest
1 голос
/ 23 марта 2012

Во время обучения в университете я столкнулся со странным поведением переменной.

/* Main parameters                                                          */
double sizeX, sizeY;      /* Size of the global domain                      */
int nPartX, nPartY;       /* Particle number in x, y direction              */
int nPart;                /* Total number of particles                      */
int nCellX, nCellY;       /* (Global) number of cells in x, y direction     */
int steps;                /* Number of timesteps                            */
double dt;                /* Stepsize for timesteps                         */
int logs;                 /* Whether or not we want to keep logfiles        */

void ReadInput(const char *fname)
{
  FILE *fp;
  char c;

  Debug("ReadInput", 0);
  if(rank == 0)
  {
    fp = fopen(fname, "r");
    if(!fp) Debug("Cannot open input file", 1);
    if(fscanf(fp, "sizeX: %lf\n", &sizeX) != 1) Debug("sizeX?",  1);
    if(fscanf(fp, "sizeY: %lf\n", &sizeY) != 1) Debug("sizeY?",  1);
    if(fscanf(fp, "nPartX:%i\n", &nPartX) != 1) Debug("nPartX?", 1);
    if(fscanf(fp, "nPartY:%i\n", &nPartY) != 1) Debug("nPartY?", 1);
    if(fscanf(fp, "nCellX:%i\n", &nCellX) != 1) Debug("nCellX?", 1); //read value is 10
    if(fscanf(fp, "nCellY:%i\n", &nCellY) != 1) Debug("nCellY?", 1);    
    if(fscanf(fp, "steps: %li\n", &steps) != 1) Debug("steps?",  1);    
//here the nCellX variable value 10 is changed somehow to 0
    if(fscanf(fp, "dt:    %lf\n", &dt)    != 1) Debug("dt?",     1);
    if(fscanf(fp, "logs:  %c\n",  &c)     != 1) Debug("logs?",   1);
    logs = (c == 'y');
    fclose(fp);
  }

  printf("(%i) reporting in...\n", rank);

  MPI_Bcast(&sizeX, 1, MPI_DOUBLE, 0, grid_comm);  
  MPI_Bcast(&sizeY, 1, MPI_DOUBLE, 0, grid_comm);
  MPI_Bcast(&nPartX,1, MPI_INT,    0, grid_comm);  
  MPI_Bcast(&nPartY,1, MPI_INT,    0, grid_comm);
  MPI_Bcast(&nCellX,1, MPI_INT,    0, grid_comm);
  MPI_Bcast(&nCellY,1, MPI_INT,    0, grid_comm);
  MPI_Bcast(&steps, 1, MPI_INT,    0, grid_comm);
  MPI_Bcast(&dt,    1, MPI_DOUBLE, 0, grid_comm);
  MPI_Bcast(&logs,  1, MPI_INT,    0, grid_comm);
  nPart = nPartX * nPartY;
  dt2 = dt * dt;
}

Учитель и я пришли к выводу, что если мы изменим имя переменной с "nCellX" на "nCellX_2", проблема исчезнети код работает как положено.Другая интересная вещь заключается в том, что только эта единственная глобальная переменная имеет эту проблему, другие переменные работают правильно.Мне было интересно, кто-нибудь сталкивался с такой проблемой.Любое руководство / объяснение будет оценено.

Если эта проблема недостаточно ясна, дайте мне знать, также, если требуется полный код, я также могу предоставить это.В общем случае код является параллельным алгоритмом Particle-in-Cell.

Ответы [ 2 ]

5 голосов
/ 23 марта 2012

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

if(fscanf(fp, "steps: %li\n", &steps) != 1) Debug("steps?",  1);

%li обозначает длинное целое число, которое может быть 64-разрядным, в то время как steps является int, который может быть 32-битным.Спецификатор формата должен быть %i вместо %li.

. Существует ли реальная проблема, зависит от среды (например, это наиболее вероятно проблема при создании 64-битного приложения).Если существует это 64-разрядное и 32-разрядное несовпадение, то вызов fscanf перезапишет память и, возможно, уничтожит любую переменную, следующую за steps в макете памяти (и это может быть nCellX).Обратите внимание, что использование опции -Wall должно предупредить вас об этой ситуации.Почему изменение имени nCellX на что-то другое должно маскировать проблему, не ясно, но может показаться, что изменение имен может привести к изменению расположения переменных в памяти;Я сомневаюсь, что это запрещено стандартом C (хотя я не смотрел).

2 голосов
/ 25 марта 2012

В качестве подтверждения комментария @Mark Wilkins & Co. я пытаюсь показать это наименование определенно может иметь эффект.

По делу:
fprintf() принимает указатель , где он хранит прочитанное. Не знает тип, на который он указывает, но возьмите определение из формата и приведите аргумент. Что-то вроде sscanf("36", "%i", &my_dest); -> number = va_arg(vl, int*);

Используйте правильные флаги для вашего компилятора, чтобы поймать это


Когда exec запускает программу, она обычно присваивает адреса неинициализированным данные (т.е. int foo;) в области, известной как BSS. (См. Рисунок 1 ниже для рисунка).

Во многих системах это будет из-за низкого адреса памяти и выше.

Чтобы продемонстрировать, что происходит (в данной системе), мы имеем следующее:

Я начинаю со следующего:

/* global scope */
unsigned char unA;
unsigned char unB;
unsigned char unC;
unsigned int  unD;

Список 1

В main() Я говорю:

unA = '1';
unB = '2';
unC = '3';
/* bit shifting the "string" NAC! into unD, reverse order as my system is LSB 
 * first (little-endian), unD becomes 558055758 => by byte ASCII !CNA */
unD = 0 | ('!' << 24) | ('C' << 16) | ('A' << 8) | 'N';

Список 2

И наведите указатель на символ без знака на unA и сбросит следующие 16 байтов, которые результат:
Дампы имеют формат [char ] или гекс с начальным нулем (% c. Или% 02x) *

 +-- Address of unA
 |
0x804b06c: 1.3.0000N.A.C.!. 2.00000000000000 
           | |     |_____|  |
           | |        |     +--- unB
           | |        +--------- unD
           | +------------------ unC
           +-------------------- unA

Список 3

Затем я меняю имя unB на un2, тот же порядок в файле:

unsigned char unA;
unsigned char un2;
unsigned char unC;
unsigned int  unD;

Список 4

Теперь мой дамп дает:

 +-- Address of unA
 |
0x804b06c: 1.3.2.00N.A.C.!. 0000000000000000
           | | |   |_____|  
           | | |      +--------- unD
           | | +---------------- unB
           | +------------------ unC
           +-------------------- unA

Список 5

Как видно, порядок адресов / выравнивания был изменен. Без изменений по типу, только по названию.

<ч />

Назначение неправильного типа:

Следующим шагом является приведение и переполнение диапазона типа. Измените un2 обратно на unB. У нас есть выравнивание, как в Список 3 .

Мы создаем функцию, которая устанавливает байты (в системе с 4 байтами / 32 бита int), высокий порядок как:

void set_what(unsigned int *n)
{
    *n = 0 | ('t' << 24) | ('a' << 16) | ('h' << 8) | 'w';
    /* or *n = 0x74616877; in an ASCII environment 
     * 0x74 0x61 0x68 0x77 == tahw */
}

Список 6

В main() мы говорим:

/* dump */
set_what((unsigned int*)&unA);
/* dump */

Список 7

и получите:

0x804b06c: 1.3.0000N.A.C.!. 2.00000000000000
0x804b06c: w.h.a.t.N.A.C.!. 2.00000000000000

Список 8

Или:

set_what((unsigned int*)&unB); -> Yield:
0x804b06c: 1.3.0000N.A.C.!. 2.00000000000000
0x804b06c: 1.3.0000N.A.C.!. w.h.a.t.00000000

set_what((unsigned int*)&unC); -> Yield:
0x804b06c: 1.3.0000N.A.C.!. 2.00000000000000
0x804b06c: 1.w.h.a.t.A.C.!. 2.00000000000000

Список 9

Как видно, данные перезаписаны, независимо от типа и того, что нет.

При некоторых условиях это может привести к SIGSEGV.


К проблемам в вашем коде, как указано в предыдущем комментарии, но я повторяю это.

В декларациях вы говорите int steps, а в fscanf() вы указываете %li который является long int, а не int. На некоторых системах это может иметь мало эффекта, но в 64-битной системе все идет плохо.

Проверить по асм:

Мы копируем код и делаем две копии, одну с long int steps; и одну с int steps; по имени A : lin_ok.c и B : lin_bad.c. Затем мы создаем некоторые ASM выход.

A $ cpp lin_ok.c > lin_ok_m32.i
A $ cpp lin_ok.c > lin_ok_m64.i
B $ cpp lin_bad.c > lin_bad_m32.i
B $ cpp lin_bad.c > lin_bad_m64.i

A $ gcc -std=c89 -m32 -S lin_ok_m32.i
A $ gcc -std=c89 -m64 -S lin_ok_m64.i
B $ gcc -std=c89 -m32 -S lin_bad_m32.i
B $ gcc -std=c89 -m64 -S lin_bad_m64.i


$ diff lin_ok_m32.s lin_ok_m64.s | head
9c9
<   .comm   steps,4,4   ; reserve 4 bytes
---
>   .comm   steps,8,8   ; reserve 8 bytes
...

Как видно, код дает указание зарезервировать 8 байтов на 64-битной и 4 на 32-битной (эта система) для steps.


Если вы используете gcc, скомпилируйте с другими флагами. Лично я использую, как правило:

gcc -Wall- Wextra -pedantic -std = c89 -o main main.c или -std=c99 если нужно.

Это предупредит вас о таких проблемах, как неправильный тип в scanf.


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

 ________________                       _________________
[                ]                     [                 ]
[                ]                     [ Physical memory ]
[ Virtual memory ] <-- Translation --> [                 ]
[     range      ]        table        { - - - - - - - - }
[________________]                     [                 ]
    |                                  [_________________]
    |
 +--+ high address : Virtual address
 |
0xF00 +-------------------+'''''''''''''''''' Runnning env
      | argv, env-vars, ..|                              |
0xBFF +-------------------+                              | ptr
      |       stack       | <- Running storage, where    |
      |...  grows down ...|  fun_a should return, local  | 0xC0000000 on
      |                   |  variables, env, ...         | linux Intel x86
      |  < huge area  >   |  New frame allocated for     |
      |                   |  recursive calls etc.        |
      |...   grows up  ...|                              |     
      |                   | <- Dynamic memory alloc.     |
      |       heap        |  malloc, etc                 |
0x9e49+-------------------+                              | 
      | double sizeX;     | <- Uninitialized data        |
bss   | ...               |           BSS 000000 ...     |
seg.  | int nCellY        |                              |
      | int steps;        |                              |
0x804c+-------------------+''''''''''''''''''''' Stored '| --- edata
data  |                   |                        on    |
seg.  | int rank = 0;     | <- Initialized data   disk   |
0x804b+-------------------+                         :    | --- etext
      | main()            |                         :    |
text  | mov ecx, edx      | <- Instructions         :    | 0x08048000 on
seg.  | ELF, or the like  |   Layout, link, etc     :    | linux Intel x86
0x8040+-------------------+ ''''''''''''''''''''''''''''''
 |
 +--- low address : Virtual address

Рис 1.

...