2012-03-23 4 views
1

私の大学での練習中、私は変数の変な振る舞いに出くわしました。変わったグローバル変数の振る舞い、変数名が変更されると問題がなくなります

/* 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_2」に「nCellX」から変数名を変更した場合、問題が消えて、期待通りのコードが動作することを結論付けています。もう一つ興味深いのは、唯一、この単一のグローバル変数は、この問題を持っていることを、他の変数が正しく動作しています。誰もこの種の問題に遭遇したのだろうかと疑問に思っていた。どんなガイドライン/説明も認められるでしょう。

この問題が十分に解明されていない場合は、完全なコードが必要な場合にもお知らせください。一般に、コードはParticle-In-Cellの並列アルゴリズムです。

+1

は、あなたのプログラムがマルチスレッドですか? 'DEBUG'のコードは何ですか? – Shahbaz

+2

コンパイラの警告が表示されますか? '-Wall -Wextra -pedantic'を使ってコンパイルして、何かが得られるかどうか調べてください。 – Collin

+0

また、 'nCellX'をいくつかの初期値に設定して定義してみてください。その名前がエラーの原因となる前に(たとえば、MPIのどこかで)初期化されていた場合は、 – Collin

答えて

5

コードの次の行が問題を引き起こしている可能性がある:stepsが32ビットであるかもしれないint、である

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

%liは64ビットであるかもしれない長い整数を示し。書式指定子は%iの代わり%liでなければなりません。

実際の問題の有無は、環境によって異なります(64ビットアプリケーションを構築する場合など)。その64ビットと32ビットのミスマッチがある場合、fscanfコールはメモリを上書きし、メモリレイアウト内のstepsに続く変数を破棄する可能性があります(それはnCellXになる可能性があります)。 -Wallオプションを使用すると、この状況について警告する必要があります。 nCellXの名前を何か別のものに変更すると、問題が隠蔽されるのはなぜか分かりませんが、名前を変更するとメモリ内の変数のレイアウトが変更される可能性があります。私はそれがCの標準によって許可されていないことを疑う(私は見ていないが)。

+0

という名前で 'nCellX'という名前を' nCellX_2 'に変更するとどうしてこの問題が解決されるのか説明できません。 – Shahbaz

+2

@Shahbazはい、できます。タイプの不一致が「未定義のビヘイビア」である場合でも、何かが起こる可能性があります。 –

+0

「何かが起こる可能性がありますが、コンパイラは決定的です。理論的には、何かが起こる可能性がありますが、実際には同じコンパイラ、同じコードで、変数名の変更を1つだけ除いて、出力は「何でも同じもの」です。 – Shahbaz

2

@マーク・ウィルキンスによるコメントの確認& Co.私は という名前の定義が効果を持つことを示しています。ケースに


fprintf()は、それが何を読む格納ポインタをとります。 が指している型は分かりませんが、フォーマットから定義を取り、 引数をキャストします。で、(つまり、int型のfoo) - sscanf("36", "%i", &my_dest);ような何か> number = va_arg(vl, int*);

使用正しいフラグが、あなたは、execはそれが一般的に初期化されていない データのアドレスを割り当てるプログラムを起動すると、この


をキャッチするコンパイラBSSとして知られている領域。 (図の下の図1を参照)。

多くのシステムでは、これはメモリ不足のアドレスからのものです。

(特定のシステム上で)何が起こるかを実証するために、以下のように、私たちは持っている:

私は次から始める:

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

リスト1

私は言います:

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にunsigned char型のポインタをポイントし 結果の次の16バイトダンプ:
ダンプ形式の[チャー<ドット>]、または先行ゼロ(%とヘクスであるがc。または%02X)* 3

は、その後、私は、ファイルにunB、同じ順序un2の名前を変更する

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

リスト:4

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

一覧私のダンプは与える:

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

一覧5

一つはアドレス/位置合わせの順序が変更された見ることができるように。 タイプの変更はありません。名前の変更のみです。


間違ったタイプの割り当て:

次のステップは、型の範囲をキャストし、オーバーフローすることです。 をunBに戻してください。 リスト3のように配置されています。main()で6

は、私たちが言う

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 */ 
} 

一覧::

我々はとして、 高い順(4バイト/ 32ビットのintをシステム上で)バイトを設定する関数を作成

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

リスト7

とget:

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()であなたはlong intないintある%li を指定します。いくつかのシステムでは、これは の影響を受ける可能性がありますが、64ビットシステムではすべてが悪くなります。 ASMで

チェック:lin_ok.cBlin_bad.c

は、我々は2つのコピー、 int steps;という名前long int steps;と1つずつのコードをコピーして作ります。次に、いくつかの出力を として作成します。一つは、コードを見ることができるように

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 
... 

stepsために32ビット (本システム)上で64ビットと4に8つのバイトを予約するように指示します。


gccを使用する場合、より多くのフラグでコンパイルしてください。個人的に私は一般的に、使用します。

gccの-Wall- Wextra -pedantic -std = c89の-oメインmain.cの または必要であれば-std=c99

これにより、scanfに間違ったタイプの警告が表示されます。


実行中のアプリケーションのレイアウトの例。システムによっては全く異なりますが、 などですが、aprox 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

+0

これはひとつの包括的な説明です。努力と説明の両方に、ありがとうございました。 – alien01

関連する問題