C 奇怪的全局变量行为,一旦变量名改变,问题就消失了
在我的大学练习中,我遇到了一个变量的奇怪行为C 奇怪的全局变量行为,一旦变量名改变,问题就消失了,c,variables,mpi,overwrite,name-conflict,C,Variables,Mpi,Overwrite,Name Conflict,在我的大学练习中,我遇到了一个变量的奇怪行为 /* Main parameters */ double sizeX, sizeY; /* Size of the global domain */ int nPartX, nPartY; /* Particle number in x, y direction
/* 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”,问题就会消失,代码也会按预期工作。另一件有趣的事情是,只有这个全局变量有这个问题,其他变量工作正常。我想知道是否有人也遇到过这种问题。如有任何指导/解释,将不胜感激
如果这个问题还不够清楚,请让我知道,如果需要完整的代码,我也可以提供。一般来说,该代码是一个粒子在单元中的并行算法 以下代码行可能导致问题:
if(fscanf(fp, "steps: %li\n", &steps) != 1) Debug("steps?", 1);
%li
表示一个长整数,可能是64位,而steps
表示一个int
,可能是32位。格式说明符应该是%i
,而不是%li
是否存在实际问题取决于环境(例如,构建64位应用程序时很可能会出现问题)。如果64位与32位不匹配,那么
fscanf
调用将覆盖内存,并可能破坏内存布局中步骤之后的任何变量(可能是nCellX
)。请注意,使用-Wall
选项应该会警告您这种情况。为什么将nCellX的名称更改为其他名称会掩盖这个问题尚不清楚,但更改名称似乎会导致内存中变量布局的改变;我怀疑C标准不允许这样做(尽管我没有查看)。可能是以下代码行导致了问题:
if(fscanf(fp, "steps: %li\n", &steps) != 1) Debug("steps?", 1);
%li
表示一个长整数,可能是64位,而steps
表示一个int
,可能是32位。格式说明符应该是%i
,而不是%li
是否存在实际问题取决于环境(例如,构建64位应用程序时很可能会出现问题)。如果64位与32位不匹配,那么fscanf
调用将覆盖内存,并可能破坏内存布局中步骤之后的任何变量(可能是nCellX
)。请注意,使用-Wall
选项应该会警告您这种情况。为什么将nCellX的名称更改为其他名称会掩盖这个问题尚不清楚,但更改名称似乎会导致内存中变量布局的改变;我怀疑C标准不允许这样做(尽管我没有看过)。作为对@Mark Wilkins&Co.评论的确认。我正试图证明这一点
肯定会有效果
关于案例:
fprintf()。它不知道
它指向的类型,但从格式中获取定义并强制转换
论点类似于sscanf(“36”、“i”和“我的目的地”)代码>->
number=va_arg(vl,int*)代码>
为编译器使用正确的标志来捕获此信息
当exec启动一个程序时,它通常为未初始化的程序分配地址
称为BSS的区域中的数据(即int-foo;)。(下图见图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';
清单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的系统上),
高阶as:
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 */
}
清单7
并获得:
0x804b06c: 1.3.0000N.A.C.!. 2.00000000000000
0x804b06c: w.h.a.t.N.A.C.!. 2.00000000000000
清单8
或:
清单9
正如人们所看到的,数据被过度写入,不管类型是什么
在某些情况下,这将导致SIGSEGV
正如前面的评论中所述,我要重复代码中的问题
在声明中说int steps
,在fscanf()
中指定%li
这是一个long int
,而不是int
。在一些系统上,这可能会
影响不大,但在64位系统上,一切都变糟了
由asm检查:
我们复制代码并复制两个副本,一个带有长int步代码>和带有
int步代码>命名为A:lin\u ok.c
和B:lin\u 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
...
图1。作为对@Mark Wilkins&Co.评论的确认。我正试图展示这种命名方式
肯定会有效果
关于案例:
fprintf()。它不知道
它指向的类型,但从格式中获取定义并强制转换
论点类似于sscanf(“36”、“i”和“我的目的地”)代码>->
number=va_arg(vl,int*)代码>
为编译器使用正确的标志来捕获此信息
当exec启动一个程序时,它通常为未初始化的程序分配地址
称为BSS的区域中的数据(即int-foo;)。(下图见图1)
在许多系统上,这将是从低内存地址开始的
演示(在给定系统上)发生的情况
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
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
...
________________ _________________
[ ] [ ]
[ ] [ 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