~ (a.k.a. bitwise COMPLIMENT or NOT operator)
The unary ~ operator flips the bits of any object it is applied to. For example: char b = 7; We know that he bits of b == 00000111. If we apply the tilde operator it will flip the bits so ~b == 11111000. Be aware this is different from the C logical "not" operator, "!" (exclamation point), which treats the entire numeral as a single Boolean value.
The NOT operator, as well as all the other bit ops are passive. They do not modify the object they are applied to.
| (a.k.a. bitwise OR )
A bit wise OR takes two bit patterns of equal length, and produces another one of the same length by matching up corresponding bits (the first of each; the second of each; and so on) and performing the logical OR operation on each pair of corresponding bits. In each pair, the result is 1 if the first bit is 1 OR the second bit is 1. Otherwise, the result is zero. For example assume:
char a == 10000100 and char b == 11100011 then a | b == 11100111
& (a.k.a. bitwise AND )
A bit wise AND takes two bit patterns of equal length, and produces another one of the same length by matching up corresponding bits (the first of each; the second of each; and so on) and performing the logical AND operation on each pair of corresponding bits. In each pair, the result is 1 if the first bit is 1 AND the second bit is 1. Otherwise, the result is zero. For example assume:
char a == 10000100 and char b == 11100011 then a & b == 10000000
^ (a.k.a. bitwise XOR a.k. exclusive or )
A bit wise XOR takes two bit patterns of equal length, and produces another one of the same length by matching up corresponding bits (the first of each; the second of each; and so on) and performing the logical XOR operation on each pair of corresponding bits. In each pair, the result is 1 if the bits differ. Otherwise, the result is zero. For example assume:
char a == 10000100 and char b == 11100011 then a ^ b == 01100111
The bit shift is sometimes considered a bit wise operation, since it operates on a set of bits. Unlike the above, the bit shift operates on the entire numeral, rather than on individual bits. In this operation, the digits are moved, or shifted, to the left or right. Registers in a computer processor have a fixed number of available bits for storing numerals, so some bits may be shifted past the "end" of the register; the different kinds of shift typically differ in what they do with the bits that are shifted past the end.
0111 << 1 = 1110 0111 >> 1 = 0011
In the first case, the leftmost 0 was shifted past the end of the register, and a new 0 was shifted into the rightmost position. Where possible you should avoid signed values as the left arg of RIGHT shift because when you RIGHT shift an unsigned value you are guaranteed to put a zero in the left most bit(s). If you RIGHT shift a signed value you cannot be sure whether the sign bit will be replicated or whether a zero will be replicated. You must be aware of this platform dependent behavior when shifting signed values. In the second case, the rightmost 1 was shifted past the end (and is often in the carry flag though that can't usually be accessed in high level languages), and the sign bit 0 was copied into the leftmost position. Multiple shifts are sometimes shortened to a single shift by some number of digits. For example:
0111 << 2 = 1100
x = y << 2;
assigns x the result of shifting y to the left by two digits, using the arithmetic shift.
A left arithmetic shift is equivalent to multiplying by two (provided the value does not overflow), while a right arithmetic shift is equivalent to dividing by two and rounding down.
Every program we have written so far has been compiled down into object code. Object code is machine code, binary 32 bit or 64 bit instructions that are diectly understood and ececuted by the CPU. n C we run directly on hardware, there is no lower layer. Our programs interact directly with hardware resources, instructions, registers and memory. Remember how C was designed, right above the hardware layer early on so many of the C operations have a 1:1 correspondence with actual hardware instructions. This contrasts sharply with Java which dos not allow the programmer to issue instructions that would correpond directly to a machine instruction or a register or something specific to the underlyuing platform.
Main difference, x86 allows you to operate directly on memory. We been diagramming memory. We understand its layout with differnt data types occupying different sized chunks of memory (sizeof operator). Instructions operate on memory. you have seen asm instructions in the other course: mov, add, sub, shl, shr, and, or, etc,. There are a handful of basic instructions that comprise all the needed logic for cgeneral compuation. The good new s is that this small basic instruction set is good enough (unless you are a physicist or a codec developer).
registers: Seen generally x86: 32 bit regs EAX: accumulator EBX: base ECX: counter EDX: data ESI: string source EDI: string destination These are the CISC instructions. You can use them as you want most of the time
x86 is an old architecture. Originally the registers were: ax, bx, cx, dx, di, si: 16 bits. These registers still exist, they are just the low 16 bits of eax, etc. We also also have ah, al, etc: High and low byte of ax. Also we now have RAX, RBX, RCX, etc for 64 bit registers. Where eax is the low 32 bits of these other registers:
RIP, RSP, RBP, RFLAGS -- 64bit EIP, ESP, EBP, EFLAGS -- 32bit
C is compiled into hardware code that interacts with registers and memory usually pretty straight forward
A command like gcc cdll.c string-Josephus.c
goes thru several stages to generate an executable.
gcc myprogram.c -S
(can put switch before or after .c file)
GCC (our debugger) uses at&t, nasm uses intel AT&T: operand size encoded in opcode e.g. add[size]. b = byte w = word l = long q = quad first operand is typically source, second is dest memory reference denoted by ()'s, offsets come before Intel: When used types are epxlicit BYTE, WORD, DWORD, QWORD BIG difference is that operands are reversed first is dest, 2nd is source memory references done with []'s How do we map C to hardware registers are typically used as scratch space In MIPS they are used as a cache, but x86 allows us to use memory directly Where are local variables? On the stack, so we reference their location on the stack offset from stack pointer Debuggers Must interact with low level code: hardware level representation generally they try to map it back to the original C code but occasionally you will need to look at ASM dumps to make sure you know what is going on Especially if you do OS work