Search code examples
ccobolgnucobol

GnuCobol calling C function with argument of type double


I have C two functions doing exactly the same thing. The only difference is their return type and first argument type:

int func1(int a, int *b) {
    if (a > 0) {
        *b = 0;
        return 1;
    }

    *b = -1;
    return 0;
}

double func2(double a, int *b) {
    if (a > (double)0.0) {
        *b = 0;
        return 1.0;
    }

    *b = -1;
    return (double)0.0;
}

Compiling them to the library:

gcc -c -static -o testlib.o testlib.c

The following COBOL code calls both functions:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CallCFunctions.
       
       ENVIRONMENT DIVISION.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  VAR-AI USAGE BINARY-SHORT SIGNED.
       01  VAR-BI USAGE BINARY-SHORT SIGNED.
       01  VAR-CI USAGE BINARY-SHORT SIGNED.
       
       01  VAR-AD USAGE BINARY-DOUBLE.
       01  VAR-BD USAGE BINARY-DOUBLE.
       01  VAR-CD USAGE BINARY-DOUBLE.
       

       PROCEDURE DIVISION.
           
           MOVE 1 TO VAR-AI.
           MOVE -10 TO VAR-BI.
           MOVE ZERO TO VAR-CI.
           CALL "func1" USING BY VALUE VAR-AI BY REFERENCE VAR-BI
               RETURNING VAR-CI
           DISPLAY "Calling 'func1'".
           DISPLAY "A = ", VAR-AI, " B = ", VAR-BI, " C = ", VAR-CI

           MOVE 1 TO VAR-AD.
           MOVE -10 TO VAR-BD.
           MOVE ZERO TO VAR-CD.
           CALL "func2" USING BY VALUE VAR-AD BY REFERENCE VAR-BD
               RETURNING VAR-CD
           DISPLAY "Calling 'func2'".
           DISPLAY "A = ", VAR-AD, " B = ", VAR-BD, " C = ", VAR-CD


       STOP RUN.

Compiling COBOL code:

cobc -x -free -o test test.cbl testlib.o

The first function works but the second generates an error:

attempt to reference unallocated memory (signal SIGSEGV) Full output

Calling 'func1'
A = +00001 B = +00000 C = +00001
Calling 'func2'

attempt to reference unallocated memory (signal SIGSEGV)

Could anyone explain what seems to be the problem?


Solution

  • Your declared variable types (USAGE) in the COBOL source are not compatible with those in the C source.

    First, the BINARY-DOUBLE type in GnuCobol is a 64-bit, native integer. From the GnuCOBOL Programmer’s Guide:

    BINARY-DOUBLE [ SIGNED ]
    ~~~~~~~~~~~~~

            Range of Values: -9,223,372,036,854,775,808 – 9,223,372,036,854,775,807
            Storage Format: Native Binary Integer

    What you want, if you're after the IEEE-754 64-bit floating point type (as is the C double) is FLOAT-LONG. From the same document:

    FLOAT-LONG
    ~~~~~~~~~~

            Range of Values: Approx. -1.797693134862316 * 10308 – 1.797693134862316 * 10308
            Storage Format: Native IEEE 754 Binary64 Floating-point

    However, I can't see that this – of itself – would cause the reported SIGSEGV error, as both memory 'units' have the same size (although the passed a value will be interpreted as a nonsense double).

    What's more likely causing that memory violation is that the type of the int arguments are also wrong: BINARY-SHORT is a 16 bit integer but the C int is 32 bits (see the same, linked document).

    You need to:

    1. Either use the BINARY-LONG (or BINARY-INT) type in your COBOL code;
    2. Or use the short type for your arguments and return type in your C code.

    As it stands, your C functions are writing (or attempting to write) 32 bits to a location that's only guaranteed to be 16 bits in size; this is undefined behaviour and is more likely the cause of the error. [You're just (un)lucky that, in the func1 call, that UB didn't trigger the memory violation!]