0

我不确定用来描述这个问题的正确术语,但我会尽力而为。

我正在编写一个玩具程序,在 test.c 中打印 12 和 13 的阶乘:

#include <stdio.h>

int main(void)
{
    printf("%ld\n", factorial(12));
    printf("%ld\n", factorial(13));
    return 0;
}

阶乘函数在与 source fact.c 的共享库中定义

long factorial(int n)
{
    long r = 1;
    while (n > 1) r *= n--;
    return r;
}

我正在使用以下命令编译共享库和我的程序:

$ gcc -shared -fPIC -o libfact.so fact.c
$ gcc -L. -lfact test.c

我在 x86-64 上,所以我希望factorial(13)(13! = 6227020800) 不会溢出 64 位长度。但是,我得到了一个奇怪的结果。

$ LD_LIBRARY_PATH=. ./a.out
479001600
1932053504

这里,1932053504 恰好是正确结果的低 32 位的十进制值。但是,如果我long factorial(int)在 test.c 的顶部插入函数原型并重新编译,我会得到正确的结果。

$ LD_LIBRARY_PATH=. ./a.out
479001600
6227020800

这给我留下了几个问题:

  1. 如果我没有在 test.c 中指定原型,假定的返回类型是什么?这个编译器依赖吗?
  2. 如果没有原型,我似乎可以将任意数量的参数传递给factorial. 这与关于 C void arguments的问题和关于函数原型的问题有关吗?
  3. 为什么链接器不抛出未定义的引用错误?
4

3 回答 3

3

从 C 标准的 1990 版本开始,如果您调用没有可见声明的函数,编译器会假定它返回 type intfactorial()因此,对于主程序中的调用,编译器很可能会将long它返回的值解释为一个int值。如果long并且int碰巧具有相同的表示,这可能会起作用。如果long比 宽int,它可能会发生工作,也可能会失败。但无论哪种方式,行为都是未定义的。

1999 版的 C 标准 (C99) 删除了“隐式 int”规则。调用没有可见声明的函数是违反约束的,需要编译器诊断。然后编译器可能会拒绝该程序,或者继续编译它——但如果是这样,则行为是未定义的。

要纠正这个问题,您需要在调用它factorial() 之前有一个可见的声明。最好的方法是创建一个头文件,factorial.h

阶乘.h:

#ifndef FACTORIAL_H
#define FACTORIAL_H

long factorial(int n);

#endif

阶乘.c:

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

long factorial(int n)
{
    printf("factorial(%d)", n);
    long r = 1;
    while (n > 1) r *= n--;
    printf(" --> %ld\n", r);
    return r;
}

主.c:

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

int main(void)
{
    printf("%ld\n", factorial(12));
    printf("%ld\n", factorial(13));
    return 0;
}

请注意,如果只有 32 位,这仍然不起作用,因为 13 阶乘超过 2 31 -1。检查系统的值和/或:longLONG_MAXsizeof (long)

printf("LONG_MAX = %ld, sizeof (long) = %d\n", LONG_MAX, (int)sizeof (long));

考虑使用long long而不是long- 或,更好的是,int64_tuint64_t,在<stdint.h>.

(据我所知,您对共享库的使用不会影响这一切。)

于 2012-11-05T07:35:45.287 回答
1
  • 如果函数返回类型未知,则编译器认为它是int. 在这种情况下发生的转换解释了printf语句的有线输出。无论如何,更喜欢使用标准long long intlong int定义的 , 来处理最大值最小值2^31 - 1

§5.2.4.2.1

— long int 类型对象的最大值

LONG_MAX +2147483647 // 2^31− 1

  • 链接器不会抛出任何未定义的引用错误,因为如果函数确实存在,它不会检查动态库。它绑定它,并在执行时尝试加载它。
于 2012-11-04T22:38:33.613 回答
-1

long在 64 位架构上不必是 8 字节,标准要求它不少于 4 字节。你有一个溢出。

于 2012-11-04T22:42:05.950 回答