0

I'm not sure of the correct terminology to use to describe this problem, but I will try my best.

I'm writing a toy program that prints the factorial of 12 and 13 in test.c:

#include <stdio.h>

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

The factorial function is defined in a shared library with source fact.c

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

I am compiling the shared library and my program with the following commands:

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

I am on x86-64, so I expect that factorial(13) (13! = 6227020800) does not overflow a 64-bit long. However, I get a strange result.

$ LD_LIBRARY_PATH=. ./a.out
479001600
1932053504

Here, 1932053504 happens to be the decimal value of the lower 32 bits of the correct result. If, however, I insert the function prototype long factorial(int) at the top of test.c and recompile, I get the correct result.

$ LD_LIBRARY_PATH=. ./a.out
479001600
6227020800

This leaves me with a couple questions:

  1. What is the assumed return type if I do not specify a prototype in test.c? Is this compiler dependent?
  2. Without a prototype, I seem to be able to pass in any arbitrary number of arguments to factorial. Is this related to this question about C void arguments and this question about function prototypes?
  3. Why doesn't the linker throw an undefined reference error?
Community
  • 1
  • 1
cyang
  • 4,884
  • 1
  • 22
  • 33
  • Why would you expect an undefined reference error? – Vaughn Cato Nov 04 '12 at 22:43
  • Somehow I was expecting that if I didn't get an undefined reference error, the function would return the correct type. I did not expect that I would not get a linker error, but also not have the correct return type. – cyang Nov 04 '12 at 22:58
  • 1
    Always, yes always, use option `-Wall` for compiling with gcc and friends, and even better also use `-std=c99`. – Jens Gustedt Nov 04 '12 at 22:58
  • C does not encode the types into the names. If it was C++, you would be right. – Vaughn Cato Nov 04 '12 at 23:00
  • @JensGustedt Lesson learned! :) I read that `-std=c99` is not fully supported on gcc, should it be of any practical concern to me if it is not? – cyang Nov 04 '12 at 23:05
  • 1
    @cyang, I don't think so. If you look into the list of things that are not completely supported, these are really very specialized things, mostly library issues. For usual daily use, gcc is C99 conforming. If you have installed, you could also use clang as a compiler. It has even better dianostics. – Jens Gustedt Nov 04 '12 at 23:10
  • Thanks @VaughnCato, could you elaborate why the type would be correct if I just had `factorial` and `main` in the same file instead of in the above arrangement? How is the type checking (is this the correct term?) different? – cyang Nov 04 '12 at 23:11
  • 1
    That's because the compiler has access to both function declarations at the same time. The compiler can only see one translation unit, which is a source file and everything it includes. The linker deals with combining the individual object files into a complete executable, but it is working on much more limited information, usually just names and addresses. It typically knows nothing about types directly. – Vaughn Cato Nov 05 '12 at 03:40

3 Answers3

3

As of the 1990 version of the C standard, if you call a function with no visible declaration the compiler assumes that it returns type int. So for the calls to factorial() in your main program, the compiler will most likely interpret the long value it returns as if it were an int value. If long and int happen to have the same representation, this is likely to work. If long is wider than int, it might happen to work, or it can fail. But the behavior is undefined either way.

The 1999 version of the C standard (C99) removed the "implicit int" rule. Calling a function with no visible declaration is a constraint violation, requiring a compiler diagnostic. The compiler may then either reject the program, or continue to compile it -- but if it does, the behavior is undefined.

To correct this, you need to have a visible declaration of factorial() before you call it. The best way to do this is to create a header file, factorial.h:

factorial.h:

#ifndef FACTORIAL_H
#define FACTORIAL_H

long factorial(int n);

#endif

factorial.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;
}

main.c:

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

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

Note that this still won't work if long is only 32 bits, since 13 factorial exceeds 231-1. Check the value of LONG_MAX and/or sizeof (long) on your system:

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

Consider using long long rather than long -- or, better yet, int64_t or uint64_t, defined in <stdint.h>.

(As far as I know, your use of a shared library doesn't affect any of this.)

Keith Thompson
  • 230,326
  • 38
  • 368
  • 578
1
  • If function return type is unknown, it is considered by the compiler to be int. The cast that occurs in this case explains the wired output of your printf statement. Anyhow, prefer using long long int, as long int are defined in the standard to handle as maximal value minimum 2^31 - 1

§5.2.4.2.1

— maximum value for an object of type long int

LONG_MAX +2147483647 // 2^31− 1

  • The linker does not throw any undefined reference error because it does not check in the dynamic library if the function really exists. It binds it, and will try to load it at execution time.
tomahh
  • 12,429
  • 3
  • 44
  • 65
  • 1
    What is meant by "it does not check in the dynamic library if the function really exists?" If I don't include the factorial function in fact.c, I get an undefined reference error. Doesn't that mean it's checking if the function exists? – cyang Nov 05 '12 at 05:13
-1

long doesn't have to be 8 bytes on 64-bit architecture, standard requires it to be not less than 4 bytes. You have an overflow.

SomeWittyUsername
  • 17,203
  • 3
  • 34
  • 78
  • Thanks @icepack, you're right about the width guarantees. However if I compile with `gcc -m64`, long should be 64 bits, but I still have the above problem. – cyang Nov 04 '12 at 23:01