Search code examples
cmoduloxc8

% operator on signed divisor needs more program memory in C


case 1:

static uint8_t i;
i % 3;     // 3 is a signed number

case 2:

static uint8_t i;
i % 3u;    // 3 is a unsigned number

With Microchip XC8 compiler, I found that case 1 needs more instruction(the program memory used is little increased). Why?

Note: 8-bit CPU. The XC8 compiler conforms to C89 standard.

assembly:

225:                   if(KEYbits.I2C == ON && h%5u == 0){
02F2  1ED2     BTFSS KEYbits, 0x5
02F3  2AF5     GOTO 0x2F5
02F4  2AF6     GOTO 0x2F6
02F5  2C75     GOTO 0x475
02F6  3005     MOVLW 0x5
02F7  00F0     MOVWF __pcstackCOMMON
02F8  3000     MOVLW 0x0
02F9  00F1     MOVWF hold
02FA  0850     MOVF h, W
02FB  00DE     MOVWF 0x5E
02FC  01DF     CLRF 0x5F
02FD  085E     MOVF 0x5E, W
02FE  00F2     MOVWF product
02FF  085F     MOVF 0x5F, W
0300  00F3     MOVWF multiplier
0301  318A     MOVLP 0xA            //here
0302  22B4     CALL 0x2B4           //here
0303  3180     MOVLP 0x0
0304  0870     MOVF __pcstackCOMMON, W
0305  0471     IORWF hold, W
0306  1D03     BTFSS STATUS, 0x2
0307  2B09     GOTO 0x309
0308  2B0A     GOTO 0x30A
0309  2C75     GOTO 0x475
226:                       h = 0;

02B4  0834     MOVF TMR4_counter, W
02B5  07DE     ADDWF 0x5E, F
02B6  0835     MOVF 0x35, W
02B7  3DDF     ADDWFC 0x5F, F
02B8  0836     MOVF 0x36, W
02B9  3DE0     ADDWFC 0x60, F
02BA  0837     MOVF 0x37, W
02BB  3DE1     ADDWFC 0x61, F
02BC  0861     MOVF 0x61, W
02BD  00B7     MOVWF 0x37
02BE  0860     MOVF 0x60, W
02BF  00B6     MOVWF 0x36
02C0  085F     MOVF 0x5F, W
02C1  00B5     MOVWF 0x35
02C2  085E     MOVF 0x5E, W
02C3  00B4     MOVWF TMR4_counter

225:                   if(KEYbits.I2C == ON && h%5 == 0){
02F2  1ED2     BTFSS KEYbits, 0x5
02F3  2AF5     GOTO 0x2F5
02F4  2AF6     GOTO 0x2F6
02F5  2C75     GOTO 0x475
02F6  3005     MOVLW 0x5
02F7  00F0     MOVWF __pcstackCOMMON
02F8  3000     MOVLW 0x0
02F9  00F1     MOVWF hold
02FA  0850     MOVF h, W
02FB  00DE     MOVWF 0x5E
02FC  01DF     CLRF 0x5F
02FD  085E     MOVF 0x5E, W
02FE  00F2     MOVWF product
02FF  085F     MOVF 0x5F, W
0300  00F3     MOVWF multiplier
0301  3186     MOVLP 0x6           //here
0302  2663     CALL 0x663          //here
0303  3180     MOVLP 0x0
0304  0870     MOVF __pcstackCOMMON, W
0305  0471     IORWF hold, W
0306  1D03     BTFSS STATUS, 0x2
0307  2B09     GOTO 0x309
0308  2B0A     GOTO 0x30A
0309  2C75     GOTO 0x475
226:                       h = 0;


0663  01F6     CLRF sign
14:             if(dividend < 0) {
0664  1FF3     BTFSS multiplier, 0x7
0665  2E67     GOTO 0x667
0666  2E68     GOTO 0x668
0667  2E70     GOTO 0x670
15:                 dividend = -dividend;
0668  09F2     COMF product, F
0669  09F3     COMF multiplier, F
066A  0AF2     INCF product, F
066B  1903     BTFSC STATUS, 0x2
066C  0AF3     INCF multiplier, F
16:                 sign = 1;
066D  01F6     CLRF sign
066E  0AF6     INCF sign, F
066F  2E70     GOTO 0x670

Solution

  • The usual arithmetic conversions are performed on the operands of %. I am guessing that i is a signed type; the result of i % 3 may differ from the result of i % 3u.

    For example, -1 % 3u is usually 0 because after conversion to unsigned int, -1 typically becomes one less than an even power of two, e.g. 65535 or 4294967295, which is always a multiple of 3.

    But -1 % 3 is either -1 or 2 in C89 (it's implementation-defined which).

    It stands to reason that the compiler might have to issue different assembly instructions for these two different cases.

    The edit to the question doesn't change this; the uint8_t is promoted to signed int, and the optimizer might not realize that it could use the "better" assembly for both cases.