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
}
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;
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.
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
User contributions licensed under CC BY-SA 3.0