在C语言函数中传递参数

3

我是一名c语言新手。我在c语言中编写了一个函数并将参数传递给它,但得到的结果与我的预期不同。

这个函数是:

void GridDim(float long1, float long2, float dx,
             float lat1, float lat2, float dy,
             float depth1, float depth2, float dh,
             int *m, int *n, int *k)
{
    *m = (int) ((long2-long1)/dx+1);
    *n = (int) ((lat2-lat1)/dy+1);
    *k = (int) ((depth2-depth1)/dh+1);
}

我使用gcc编译它:

gcc -c GridDim.c

然后我使用该对象文件作为主文件,并编译该文件。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
 int m,n,k;
 GridDim(20.0,30.0,0.1,10.0,15.0,0.1,5.0,20.0,5.0,&m,&n,&k);
 printf("m= %d\nn= %d\nk= %d\n", m, n, k);
 return 1;
}

gcc test.c -o test GridDim.o

我运行了这个命令,但是没有得到正确的答案。 有人知道为什么吗? 正确的答案应该是 m=101 n=51 k=4 但是我得到的是 m=1 n=1 k=-12


7
什么才是“正确答案”? - Sadique
1
只是一个猜测,您可能忘记在 d?+1 项周围加上 ()(例如 (long2-long1)/(dx+1))了? - ChrisWue
1
@PengyuCHEN,或者EXIT_SUCCESS,这样意图更加清晰。 - Jens Gustedt
答案应该是 m=101 n=51 k=4,但我得到的是 m=1 n=1 k=-12 ??? - Reza MN
1
我将问题回滚到第三个版本,因为LihO的更改改变了源代码和报告的观察结果,从而使问题不再是OP所问的问题。 OP报告的结果表明缺少函数声明,而LihO的更改通过将源合并为一个模块来隐藏这一点。 编辑格式是可以的,但根本上更改问题是不可以的。 - Eric Postpischil
显示剩余3条评论
4个回答

4
你需要为GridDim函数声明一个函数原型,并将其包含在包含主方法的文件中。
原因是,除非编译器有一个声明参数类型的函数原型,否则它会将所有浮点数提升为双精度。在运行时,你的浮点数被提升为双精度,然后传递给GridDim函数,它读取双精度数的前半部分并将其解释为浮点数。如果你在GridDim内部打印值,你会看到浮点数以损坏的值进入。
如果你在头文件中声明你的方法,例如GridDim.h:
#ifndef __GRID_DIM_DOT_H__
#define __GRID_DIM_DOT_H__

extern void GridDim(float long1, float long2, float dx, float lat1, float lat2, float dy, float depth1, float depth2, float dh, int* m, int* n, int* k);

#endif/*__GRID_DIM_DOT_H__*/

请将以下内容添加到GridDim.c中(以确保定义与声明匹配):

...和#include

#include <stdio.h>
#include "GridDim.h"

void GridDim(float long1, float long2, float dx,
             float lat1, float lat2, float dy,
             float depth1, float depth2, float dh,
             int *m, int *n, int *k)
{
    printf("long1 =  %10.6f\n", long1);
    printf("long2 =  %10.6f\n", long2);
    printf("dx =     %10.6f\n", dx);
    printf("lat1 =   %10.6f\n", lat1);
    printf("lat2 =   %10.6f\n", lat2);
    printf("long1 =  %10.6f\n", dy);
    printf("depth1 = %10.6f\n", depth1);
    printf("depth2 = %10.6f\n", depth2);
    printf("dh =     %10.6f\n", dh);

    *m = (int) ((long2-long1)/dx+1);
    *n = (int) ((lat2-lat1)/dy+1);
    *k = (int) ((depth2-depth1)/dh+1);
}

...并将其#include到Main.c中,以确保调用与声明匹配:

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

#include "GridDim.h"

int main()
{
 int m,n,k;
 GridDim(20.0f,30.0f,0.1f,10.0f,15.0f,0.1f,5.0f,20.0f,5.0f,&m,&n,&k);
 printf("m= %d\nn= %d\nk= %d\n", m, n, k);
 return 1;
}

如果你正确传递参数给GridDim函数,那么这些参数将被正确地传递。我添加了一些printf语句,以便您可以看到这一点。

如果在Main.c中注释掉#include "GridDim.h",那么您将会看到当前代码版本中正在发生的事情:

long1 =    0.000000
long2 =    0.000000
dx =      -0.000000
lat1 =     0.000000
lat2 =     0.000000
long1 =   -0.000000
depth1 =   0.000000
depth2 =   0.000000
dh =       0.000000
m= 1
n= 1
k= -2147483648

使用 #include 后,输出结果如下:
long1 =   20.000000
long2 =   30.000000
dx =       0.100000
lat1 =    10.000000
lat2 =    15.000000
long1 =    0.100000
depth1 =   5.000000
depth2 =  20.000000
dh =       5.000000
m= 101
n= 51
k= 4

对于头文件的使用,我个人喜欢使用 [+1]。 - Gangadhar

3
您缺少函数声明。请在主函数之前声明该函数。Declare function before main()
void GridDim(float long1, float long2, float dx,
           float lat1, float lat2, float dy,
             float depth1, float depth2, float dh,
             int *m, int *n, int *k);

同时编译两个文件。
gcc main.c griddim.c -o result
./result

1
@P0W 这是因为在C语言中可以使用没有原型的函数,但我认为Gangadhar已经正确地解决了问题。然而,请在头文件使用方面添加一些内容,Gangadhar,因为将前向声明放入除一个头文件外的其他文件中,而该头文件也被包含在具有函数定义以进行签名检查的文件中,这是一条通往灾难的必经之路(这就是我还没有点赞的原因)。 - cmaster - reinstate monica
2
@P0W:在旧的C实现中,即使缺少函数声明也有可能编译和运行,并且这可能会产生OP报告的结果,因为在缺少声明的情况下参数不会被正确传递。到目前为止,这个答案是唯一解释观察到结果的答案。 - Eric Postpischil
1
@P0W:结果取决于实现。在使用IEEE 754和正确舍入的C实现中,结果为101、51和4。低质量的C实现可能会给出不同的结果。然而,这些结果是正确的,因为它们是使用精确数学得到的结果,这有点幸运。当源文本“0.1”转换为“double”时,它被四舍五入为可表示的值。当10除以该值时,商被向下舍入。这两个误差恰好抵消,产生了所需的100。 - Eric Postpischil
1
@Gangadhar,这在ISO/IEC 9899:201x6.5.2.2函数调用章节中:如果表示被调用函数的表达式没有包含原型,则对每个参数执行整数提升,并将类型为float的参数提升为double。这些被称为默认参数提升。 - starrify
@EricPostpischil:“结果取决于实现”是正确的。但是关于“产生恰好100,如所需”的问题以及OP的“答案应为m=101..”,没有任何答案考虑到这一点。 - P0W
显示剩余7条评论

1
正如我在评论中所说,C 中的类型转换 (int) 会进行修整,例如 (int)0.9 == 0。在这里,您可能希望使用由 math.h 提供的函数 round 来解决问题。
以下代码可以解决您的问题。
#include <math.h>
void GridDim(float long1, float long2, float dx,
             float lat1, float lat2, float dy,
             float depth1, float depth2, float dh,
             int *m, int *n, int *k)
{
    *m = round((long2-long1)/dx+1);
    *n = round((lat2-lat1)/dy+1);
    *k = round((depth2-depth1)/dh+1);
}

编辑:

根据标准文档,第6节ISO/IEC 9899:TC2规定如下:

6.3.1.4 实浮点数和整数

1 当实浮点类型的有限值转换为除了_Bool以外的整数类型时,小数部分被舍弃(即值向零取整)。如果整数部分的值不能由整数类型表示,则行为未定义。50)

再次编辑:

然而,我必须说您的原始代码在我的机器上产生了正确的答案。请注意,这个问题实际上是由机器相关的浮点数计算引起的:

假设您正在计算1.05.0的乘积:float x = 1.0 * 5.0。由于精度问题,您的实际结果可能是5.0000024.999998,这取决于运行此代码的平台。

当数值为5.000002时,使用强制类型转换(int)x会将结果转换为5,但是当数值为4.999998时,它会被转换为4。而round函数总是产生您期望的答案。
对不起,我没有指出您问题的确切原因!您缺少函数定义。在这里,我支持@Gangadhar的答案。

1
这并没有解释问题中报告的结果,“m=1 n=1 k=-12”。 - Eric Postpischil
1
@EricPostpischil 是的,没错。我认为Gangadhar给出了正确的答案。 - starrify

0

在将值存储到变量之前,您必须取ceil值。因为在c中,如果您将0.9999值转换为int,则会转换为零,因此在存储值之前,您必须取ceil值。

void GridDim(float long1, float long2, float dx,
             float lat1, float lat2, float dy,
             float depth1, float depth2, float dh,
             int *m, int *n, int *k)
{

   *m = (int) (ceil((long2-long1)/dx)+1);
   *n = (int) (ceil((lat2-lat1)/dy)+1);
   *k = (int) ((depth2-depth1)/dh+1);
}

这并没有解释问题中报告的结果,“m=1 n=1 k=-12”。 - Eric Postpischil
1
我不是一个踩票者,但我会留下评论,因为我认为Srihari在代码中提到了一个额外的问题,他几乎是正确的。当(long2-long1)恰好可被dx整除时,“*m =(ceil((long2-long1)/ dx));”将产生更好的答案。 - richj
@richj:即使浮点数是问题所在,ceil也不是正确的解决方案。那会使答案过大。 - Eric Postpischil
@EricPostpischil:我可以理解如果我们真的不介意在网格的远端创建一个额外的单元格列来容纳条目,它可能会使网格变得太小。请问你有一个答案过大的测试用例吗? - richj
如果您将前三个参数设置为GridDim0.f,210.f,2.1f并使用正确舍入的IEEE 754 C实现编译和执行,则使用ceil计算m的结果为102,而精确计算为101。这是因为源文本中的2.1f在转换为float时必须稍微向下舍入,且商210.f / 2.1f产生的数字略大于100,所以ceil使其为101(然后添加1,使其成为102)。 - Eric Postpischil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接