Call a function multiple times with the same input but get different return value

2

I'm using GNU C in Ubuntu 14.0.4. I wrote a CRC_XOR() function and call it with the same input argument multiple times. But It's wierd, each call may sometimes get different result. What's going wrong? Sample code is here:

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

char CRC_XOR(char *as_Pkt,int ai_len);
void DO_Get_CRC(void);
int main(void)
{
    DO_Get_CRC(); //get 01 00 02 03
    DO_Get_CRC(); //get 01 00 02 03
    DO_Get_CRC(); //get 01 00 02 00  (strange?)  
    DO_Get_CRC(); //get 01 00 02 03
    DO_Get_CRC(); //get 01 00 02 00  (strange?) 
    DO_Get_CRC(); //get 01 00 02 03
    DO_Get_CRC(); //get 01 00 02 00  (strange?) 
    exit(0);
}
/*
    use same input to invoke CRC_XOR()
*/
void DO_Get_CRC(void)
{
    char  ls_pkt[20];
    int   li_idx;
    short li_max = 512;
    int   li_len = 0;

    ls_pkt[li_len++] = 0x01;  //station ID
    ls_pkt[li_len++] = 0x00;  //length-low byte
    ls_pkt[li_len++] = 0x02;  //length-high byte
    ls_pkt[li_len++] = CRC_XOR(ls_pkt,li_len);
    ls_pkt[li_len]   = 0;
    for (li_idx=0; li_idx<li_len;li_idx++) {
         printf("%02X ", ls_pkt[li_idx]); //display in hexdigits
    }

    printf("\n");
}
/*
    return 1 byte of CRC by XOR byte array
*/
char CRC_XOR(char *as_Pkt,int ai_len)
{
    int  li_idx;
    char lc_CRC = 0x00;

    for (li_idx=0; li_idx < ai_len; li_idx++){
        lc_CRC ^= as_Pkt[li_idx]; //XOR each byte
    }
    return (char)(lc_CRC & 0x000000FF); //return CRC byte
}
c
xor
crc
asked on Stack Overflow Aug 10, 2017 by Victor Huang • edited Aug 10, 2017 by Victor Huang

3 Answers

8

You have undefined behavior.

The reason is because there is no sequence point with assignment.

For

ls_pkt[li_len++] = CRC_XOR(ls_pkt,li_len);

you don't know if li_len++ happens before or after the evaluation of li_len in the function call. That in turns might mean that li_len might be equal to 4 which means your CRC_XOR function will use the as of yet uninitialized ls_pkt[4].

Since ls_pkt[4] is uninitialized before the assignment, its value will be indeterminate, and might be seemingly random.

The simple solution is to increase li_len after the assignment:

ls_pkt[li_len] = CRC_XOR(ls_pkt,li_len);
++li_len;
1

I couldn't originally reproduce in 64-bit mode in my x86, but it seems to be easy to reproduce on my computer in 32-bit mode; perhaps the different stack width is the reason.

% gcc crc.c -m32
% ./a.out  
01 00 02 03 
01 00 02 00 
01 00 02 03 
01 00 02 00 
01 00 02 03 
01 00 02 00 
01 00 02 03 

I've numbered the lines here so one can easily see how they correspond to the .loc lines from the assembler output:

20  void DO_Get_CRC(void)
21  {
22      char  ls_pkt[20];
23      int   li_idx;
24      short li_max = 512;
25      int   li_len = 0;
26
27      ls_pkt[li_len++] = 0x01;  //station ID
28      ls_pkt[li_len++] = 0x00;  //length-low byte
29      ls_pkt[li_len++] = 0x02;  //length-high byte
30      ls_pkt[li_len++] = CRC_XOR(ls_pkt,li_len);
31      ls_pkt[li_len]   = 0;
32      for (li_idx=0; li_idx<li_len;li_idx++) {
33           printf("%02X ", ls_pkt[li_idx]); //display in hexdigits
34      }
35
36      printf("\n");
37  }

Compiling this into assembly, we get the following assembler output:

DO_Get_CRC:
.LFB3:
        .loc 1 21 0
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        pushl   %esi
        pushl   %ebx
        subl    $48, %esp
        .cfi_offset 6, -12
        .cfi_offset 3, -16
        call    __x86.get_pc_thunk.bx
        addl    $_GLOBAL_OFFSET_TABLE_, %ebx
        .loc 1 21 0
        movl    %gs:20, %eax
        movl    %eax, -12(%ebp)
        xorl    %eax, %eax
        .loc 1 24 0
        movw    $512, -42(%ebp)
        .loc 1 25 0
        movl    $0, -36(%ebp)      // int li_len = 0
        .loc 1 27 0
        movl    -36(%ebp), %eax    // eax = li_len
        leal    1(%eax), %edx      // edx = eax + 1
        movl    %edx, -36(%ebp)    // li_len = edx
        movb    $1, -32(%ebp,%eax) // ls_pkt[eax] = 1
        .loc 1 28 0
        movl    -36(%ebp), %eax    // eax = li_len
        leal    1(%eax), %edx      // edx = eax + 1
        movl    %edx, -36(%ebp)    // li_len = edx
        movb    $0, -32(%ebp,%eax) // ls_pkt[eax] = 0
        .loc 1 29 0
        movl    -36(%ebp), %eax    // eax = li_len
        leal    1(%eax), %edx      // edx = eax + 1
        movl    %edx, -36(%ebp)    // li_len = edx
        movb    $2, -32(%ebp,%eax) // ls_pkt[eax] = 2
        .loc 1 30 0
        movl    -36(%ebp), %esi    // esi = li_len
        leal    1(%esi), %eax      // eax = esi + 1
        movl    %eax, -36(%ebp)    // li_len = eax --li_len is now **4**
        subl    $8, %esp
        pushl   -36(%ebp)          // push the value of li_len (2nd arg)
        leal    -32(%ebp), %eax    // load address of ls_pkt
        pushl   %eax               // and push ls_pkt (1śt arg)
        call    CRC_XOR            // call CRC_XOR

The post-increments are encoded as the "load effective address" instruction, it is a common trick in X86 assembler to use the address calculation hardware for constant arithmetic as the instructions are smaller. Anyway, there are a total of 4 leal 1(%XXX), %YYY followed by movl %YYY, -36(%ebp), where -36(%ebp) is the location of the variable li_len, meaning that li_len was incremented 4 times before its value is pushed onto the stack as the argument for CRC_XOR. However if the code is mutated elsewhere it easily happens so that the same compiler produces code that increments li_len only 3 times before calling the function, proving that they're indeed indeterminately sequenced.

answered on Stack Overflow Aug 10, 2017 by Antti Haapala
0

Remove ; in this line

void DO_Get_CRC(void); --> void DO_Get_CRC(void)

Modify this line, because you wanted to pass li_len but you passing li_len+1 because of ++ while assigning

ls_pkt[li_len++] = CRC_XOR(ls_pkt,li_len); -->  ls_pkt[li_len++] = CRC_XOR(ls_pkt,li_len-1);

Its works fine without any issue main.c:31:27: note: each undeclared identifier is reported only once for each function it appears in
sh-4.2$ gcc -o main *.c
sh-4.2$ main
01 00 02 03
01 00 02 03
01 00 02 03
01 00 02 03
01 00 02 03
01 00 02 03
01 00 02 03

answered on Stack Overflow Aug 10, 2017 by Thiru Shetty • edited Aug 10, 2017 by Thiru Shetty

User contributions licensed under CC BY-SA 3.0