Path: csiph.com!usenet.pasdenom.info!aioe.org!.POSTED!not-for-mail From: "Rod Pemberton" Newsgroups: comp.os.msdos.programmer Subject: Re: Smaller C compiler Date: Sat, 07 Dec 2013 13:48:37 -0500 Organization: Aioe.org NNTP Server Lines: 161 Message-ID: References: <2aec554a-416a-48b3-94fb-3ec8c8cf215d@googlegroups.com> <943963d2-d981-4863-831f-c74b57a4db75@googlegroups.com> NNTP-Posting-Host: CNsg4fVcCsvs3UaOgZtQCw.user.speranza.aioe.org Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii; format=flowed; delsp=yes Content-Transfer-Encoding: 7bit X-Complaints-To: abuse@aioe.org User-Agent: Opera Mail/12.16 (Linux) X-Notice: Filtered by postfilter v. 0.8.2 Xref: csiph.com comp.os.msdos.programmer:1127 On Sat, 07 Dec 2013 01:23:14 -0500, Alexei A. Frounze wrote: > On Friday, December 6, 2013 9:19:10 AM UTC-8, Rod Pemberton wrote: >> On Fri, 06 Dec 2013 00:37:26 -0500, Alexei A. Frounze >> <...@gmail.com> wrote: >> > On Thursday, December 5, 2013 3:33:51 AM UTC-8, Rod Pemberton wrote: >> >> OpenWatcom v1.3 results of ~0u and with shifts: >> >> >> >> wcl/l=dos >> >> >> >> ~0u 8000ffff >> >> ~0u<<15 80000000 >> >> ~0u<<16 0000 >> >> ~0u<<8<<7 0002 >> >> ~0u<<8<<8 6322 or 6333 >> > >> > How come 16-bit ints look like 32-bit? Where's the garbage coming >> from? >> > I mean "8000" in "8000ffff". >> > >> >> It's coming from the program when it prints the value. >> >> Specifically, it's from a "%04lx" to a printf(). It overflowed. E.g., >> >> printf("%04lx\n",~0u); >> >> Yes, in theory, it should truncate to only four digits for display... > > In theory and in practice you shouldn't lie to printf() about the type > of the optional parameters. If you write "%lx" but then supply something > that's not a long integer (unsigned, AFAIR), you get undefined behavior > from printf(). 0 and 0u are an int and an unsigned int, and not any kind > of long int. > As a practical matter, with or without a cast, the argument is converted to a hex unsigned long for display by the compiler. Whether that conversion is officially classified as "undefined behavior", or classified as an "implicit cast", it should still convert types correctly for the situation, or it should cause compilation to error. If the OW compiler had determined a type mismatch, it would've warned about it. I.e., this implies it views the types as equivalent or as a valid conversion, or promotion. If a cast or "%hx" or &0xFFFF had been used, you might not have seen this issue. To me, the 8-digit displayed values implies the compiler is actually using a 32-bit signed type for ~0u and etc values instead of 16-bit unsigned. When the converted result fits into the display format "%04lx", which is defined behavior, we're seeing four digits as expected. When the converted result doesn't fit the display format, which is officially undefined behavior, e.g., when signed or larger than 64K, it displays the entire, non-truncated, value using eight digits. >> >> wcl386/l=dos4g >> >> >> >> ~0u ffffffff >> >> ~0u<<31 80000000 >> >> ~0u<<32 ffffffff >> >> ~0u<<8<<12<<11 80000000 >> >> ~0u<<8<<12<<12 00000000 >> >> >> >> Obviously, some things are not correct or computing as expected. >> > >> > For one thing, you shouldn't be expecting much from shifting a >> > 16-bit int by 16 bit positions or from shifting a 32-bit int by >> > 32 bit positions. Both invoke undefined behavior. >> > >> >> I shouldn't? You're the one doing so... <<8<<12<<12. Yes? > > Yes and no. <<8<<12<<12 isn't necessarily equivalent to <<(8+12+12). Just as you "shouldn't lie to printf", you shouldn't abuse the poorly, and not widely implemented, ANSI C concept of "sequence points" in an attempt to avoid undefined behavior. Alternately, you shouldn't abuse known limits of limits.h to attempt to reason your way around undefined behavior, either. A C compiler is not as smart as you. It does things one way for all apparently equivalent situations. It's goal is to get reasonably C compliant assembly in the most sane way possible. So, if undefined behavior exists in one instance, that same undefined behavior situation will likely be valid for other instances which are effectively the same, but not undefined. I.e., if <<32 on 32-bit is undefined, then effectively <<8<<12<<12 is too. This is also an example why you have to consider what is going to be done by the C compiler at the assembly level. No matter how <<8<<12<<12 is implemented, it will zero a 32-bit integer. > The restriction on the shift count applies to one shift. Hence, with > unsigned int being at least 16 bits long, I can shift by however many > positions I want, provided that I don't shift by more than 15 at a time. That officially avoids undefined behavior in C. It doesn't avoid overflowing integers at the assembly level, which someone might claim is the basis for undefined behaviors in C... ;-) > C typically translates << to a single shift instruction, It may. The underlying architecture is assumed to be unknown to C. So, the C compiler is free to implement shifts as whatever it takes. It can do multiple to single, or single to multiple, or even other non-shift manipulations. > without generating any code to check the shift count at run time. True. That's checked at compile time. > In most CPUs, > shift instructions only take the least significant bits of the shift > count (4 bits for a shift of a 16-bit value, 5 bits for a shift of a > 32-bit value and so on) Well, it seems to be 5 bits on x86, for both regular and double shifts. > and ignore the rest. The assembly instruction may ignore the remainder of the bits effectively imposing a per instruction shift limit. That just means more instructions are required to implement larger shifts. The C compiler is required to implement the total shift count using as many assembly instructions as is needed. The C compiler can't just ignore 3 bits of shift for <<8 by truncating it to <<5 because the CPU only supports <<5... > So, effectively, your <<32 reduces to shl reg, 0. No. Perhaps, you're thinking of "rol reg" instead of "shl reg"? ;-) While the effective shift count for "shl reg, 0" is the same as <<32 due to wrap around, "shl reg, 0" doesn't actually *clear* the register. It leaves it alone. SHL shifts in a cleared bit per shift. ROL rotates one bit per shift. A shift left of 32 bits or more will zero a 32-bit register. So, <<32 would effectively reduce to equivalent of 'xor reg, reg', or 'mov reg, 0'. Of course, most C compilers would use multiple shl shifts to shift 32 times, but some might optimize to a register clear. > That's hardly what you want. True. Rod Pemberton