Hi, I get a curious result when compiling such a following code :
typedef union { unsigned char cCtrlFullByte; struct { unsigned char bEnable : 1; unsigned char cUnused : 7; } cCtrlStruct; } CtrlUnion; void main (void) { unsigned char dummy = 0x55; CtrlUnion xdata bitUnion; bitUnion.cCtrlStruct.bEnable = dummy & 0x40; return; }
MOV A,#0x55 ANL A,#0x00 MOV R7,A MOV DPTR, #0x0000 MOVX A,@DPTR ANL A,#0xFE ORL A,R7 MOVX @DPTR, A
Jon, thank you for your comments. My [rant] was just to get something off my chest – I didn't really expect a reply. I guessed that the reasons for sfr and sbit were historical and I don't seriously expect Keil to go making huge changes and certainly not any that would affect existing applications. I am still left with a couple of gripes that I feel are legitimate and should be considered by Keil. I believe that these suggestions could be implemented without adversely affecting existing applications. 1-bit fields in structures are very common is my applications and I would think that would go for most other C51 applications too. Whatever failings the 80C51 may have, it is actually quite good at bit-twiddling and that can be a reason for choosing to use a processor from the family. Keil C51, however, does not seem to generate efficient code for 1-bit fields; C51 seems to generate much the same sort of code regardless of the number of the bits in a field. It would be a big help if the compiler/optimiser could treat 1-bit fields as a special case and use bit addressing instructions to accesses a copy of a variable held in, say, the Acc or B register. This would result in faster and more compact code. Also, it would encourage programmers to use bit fields rather than masks – the former being clearer and less error-prone. bit defines a memory type, but C51 does not allow a type to be specified for a variable stored in bit memory. Of course, this does not actually stop me writing the software that I want, but if I move a Boolean variable between bit memory and any other memory type I have to add or remove a type definition. Variables that do not have a type make me feel uncomfortable – if I can assign a type to a 1-bit field, why can't I assign a type to a bit? If enumerated types with exactly two possible states could, optionally, be assigned to bit variables, then I could check for breaches of strong typing rules – and that is a software quality issue for me. Finally, a compiler option could allow C51 automatically to assign enumerated types (with exactly two possible states) to bit memory when the memory model is small. This would make it quicker and easier to write small applications without having to worry about different types of memory model – an important consideration for Keil beginners. The resulting applications would be faster, more compact and more portable. If you are still reading, thanks for your attention. I realise that all this is a lot easier to write about than to actually do! Keil C51 is an excellent compiler – I just love it so much that I want it to be perfect!
I don't understand why a 1-bit variable will get a different result depending on its location mapping. It's not a question of code size but only of result ! If you write
unsigned char dummy = 0x55; bit bitValue; void main (void) { bitValue = dummy & 0x74; return; }
MOV A, dummy ANL A,#0x74 ADD A, #0xFF MOV bitValue, C
typedef struct { unsigned char bEnable : 1; unsigned char cUnused : 7; } BitFieldStruct; unsigned char dummy = 0x55; BitFieldStruct bdata bitUnion; void main (void) { bitUnion.bEnable = dummy & 0x74; return; }
MOV A, dummy ANL A,#0x00 MOV R7, A MOV A, bitUnion ANL A,#0xFE ORL A, R7 MOV bitUnion, A
I guess, it can be understood like this: bit and unsigned char:1 are DIFFERENT types: bit can be seen similar to the bool type in C++: 0 is false, anything else is true. Integers of 1 bit size are not boolean. Rather do they still remain integers restricted to the range {0,1}. They are "arithmetical", bits on the other hand are "logical". A numerical value casted to bit therefore must yield 1 (i.e. true) if it would succeed as a proposition in a control statement, otherwise it must yield 0 (i.e. false). A numerical value casted to an integer with less size (e.g 1) however must be stripped off the "excessive" bits in its binary representation, leaving possibly only the LSB. So these propositions hold:
(bit)2 == 1; /*logical use */ (uchar:1)2 == 0; /* arithmetic use (this isn't C-Syntax, I know)*/
bit xdata externalBit; /*why not?*/
Arnaud, the compiler is generating correct code. You are casting from an 8-bit unsigned char to a 1-bit unsigned char. The rule when casting from a large unsigned type to a smaller unsigned type of variable is simply to discard the most significant bits. You will find that the same sort of thing happens when you cast from an unsigned int to an unsigned char – the 8 most significant bits of the unsigned int are discarded. Norbert is quite correct to say that bit memory and and a 1-bit bit field are not the same thing. In C you have to be vary careful with Booleans – only logical operators and some functions return truly Boolean results. However:
0000 900000 R MOV DPTR,#dummy 0003 E0 MOVX A,@DPTR 0004 5400 ANL A,#00H 0006 FF MOV R7,A 0007 E500 R MOV A,bitUnion 0009 54FE ANL A,#0FEH 000B 4F ORL A,R7 000C F500 R MOV bitUnion,A 000E ?C0085: 000E 22 RET
…. unsigned char dummy = 0x55; BitFieldStruct bdata bitUnion; void main (void) { bitUnion.bEnable = dummy & 0x71; return; }
0000 900000 R MOV DPTR,#dummy 0003 E0 MOVX A,@DPTR 0004 5401 ANL A,#01H 0006 FF MOV R7,A 0007 E500 R MOV A,bitUnion 0009 54FE ANL A,#0FEH 000B 4F ORL A,R7 000C F500 R MOV bitUnion,A 000E ?C0085: 000E 22 RET
typedef struct { boolean bEnable : 1; unsigned char cUnused : 7; } BitFieldStruct; unsigned char dummy = 0x55; BitFieldStruct bdata bitUnion; void main (void) { bitUnion.bEnable = ( dummy & 0x74 ) != 0; return; }
0000 900000 R MOV DPTR,#dummy 0003 E0 MOVX A,@DPTR 0004 5474 ANL A,#074H 0006 6004 JZ ?C0085 0008 7F01 MOV R7,#01H 000A 8002 SJMP ?C0086 000C ?C0085: 000C 7F00 MOV R7,#00H 000E ?C0086: 000E EF MOV A,R7 000F 5401 ANL A,#01H 0011 FF MOV R7,A 0012 E500 R MOV A,bitUnion 0014 54FE ANL A,#0FEH 0016 4F ORL A,R7 0017 F500 R MOV bitUnion,A 0019 ?C0087: 0019 22 RET
0000 900000 R MOV DPTR,#dummy 0003 E0 MOVX A,@DPTR 0004 5474 ANL A,#074H 0006 6004 ADD A,#FFH 0012 E500 R MOV A,bitUnion 0014 54FE MOV Acc.0,C 0017 F500 R MOV bitUnion,A 0019 ?C0087: 0019 22
Graham, Those are all good ideas. Bit variables, are unfortunately, and oddity of the 8051 (and C16x) architecture. They are only included in the compiler because SETB and CLR instructions are faster than the read-modify-write equivalents. As for using the CLR and SETB instructions on bitfields, I'm not sure that this would actually improve the performance. If you consider that the unsigned char/int of the bitfield must be read and written back, that is 2 instructions. A set (LOGICAL OR) operation only requires 1 instruction to set up to 8 bits in a byte. And that would be faster than using several SETB instructions. For the case of setting a single bit, the code generated would be the same. A clear operation (LOGICAL AND) is the same--only one instruction is required to clear 1-8 bits. A clear and set operation (LOGICAL AND plus LOGICAL OR) requires 2 instructions, the AND mask and the OR mask--but only for multi-bit bitfields. Anyway, we'll keep looking at this to see if we can improve code generation for these types of operations. As for the bit typing, you can use the BDATA memory space to place typed variables in the bit-addressable area. But I'm not sure this is what you want. Jon
OK, I understand the thought process but I guess that very few people do 1-bit arithmetic. But that the way Keil choose and it is one solution. Not the way for me, but that's the life ! ;-) Thanks all for your help and explanations Arnaud
Jon, thanks for your comments. My apologies to Arnaud for high jacking his thread. At the risk of becoming tedious, I decided to do some experiments which have concluded with the following item of test code. The experiment has convinced me that bit fields are not accessed efficiently. I have been able to go and look at our latest application, it is about 60K bytes long and contains a number of modules doing a wide variety of things and written by a number of different programmers. I searched for the simple case of testing a 1-bit bit field in a simple if statement, I found just under 500 instances. An average saving of just 3 instructions in each case would give a code reduction of 2.5% which I think is significant. There are in the 60K application, at least as many logical operations as arithmetic operations – quite possibly more. Many of them involve 1-bit bit fields. Many things in the same application are achieved by means of masks whereas using bit fields would be more elegant, but currently will generate slower code.
typedef enum { FALSE, TRUE } boolean; typedef struct { boolean bit0 : 1; boolean bit1 : 1; boolean bit2 : 1; boolean bit3 : 1; boolean bit4 : 1; boolean bit5 : 1; boolean bit6 : 1; boolean bit7 : 1; } bit_field_type; extern bit do_stuff(); main( void ) { bit /*boolean*/ r, s, t; data boolean x; bit_field_type byte; r = s | t; r = s || t; byte.bit0 = TRUE; byte.bit1 = r; s = byte.bit2; byte.bit2 = s | !t; byte.bit7 = byte.bit5 && byte.bit6; if( byte.bit3 ) { r = TRUE; } }
r = s | t;
; SOURCE LINE # 25 0000 A200 R MOV C,t 0002 7200 R ORL C,s 0004 9200 R MOV r,C
r = s || t;
0006 200003 R JB s,?C0003 0009 300003 R JNB t,?C0001 000C ?C0003: 000C D3 SETB C 000D 8001 SJMP ?C0002 000F ?C0001: 000F C3 CLR C 0010 ?C0002: 0010 9200 R MOV r,C
byte.bit0 = TRUE;
; SOURCE LINE # 28 0012 E500 R MOV A,byte 0014 4401 ORL A,#01H 0016 F500 R MOV byte,A
byte.bit1 = r;
; SOURCE LINE # 29 0018 A200 R MOV C,r 001A E4 CLR A 001B 33 RLC A 001C 5401 ANL A,#01H 001E FF MOV R7,A 001F 25E0 ADD A,ACC 0021 FF MOV R7,A 0022 E500 R MOV A,byte 0024 54FD ANL A,#0FDH 0026 4F ORL A,R7 0027 F500 R MOV byte,A
0018 A200 R MOV C,r 001A E500 R MOV A,byte 001C 54FD MOV Acc.1,C 001E F500 R MOV byte,A
if( byte.bit3 )
; SOURCE LINE # 36 006F FF MOV R7,A 0070 13 RRC A 0071 13 RRC A 0072 13 RRC A 0073 541F ANL A,#01FH 0075 30E002 JNB ACC.0,?C0007
006F E500 R MOV A,byte 0071 30E002 JNB ACC.3,?C0007