2's complement is just a map between decimal and binary number.
The compiler is implementing this mapping by translating literal number to corresponding binary, for example -3 to 0xFFFFFFFD(as can be seen in disassembly), and producing machine code that is consistent with 2's complement representation. For example, when it tries to execute 0-3, it should choose a instuction that should produce 0xFFFFFFFD by taking 0x00000000 and 0x000000003 as arguments.
The reason it chooses SUB which is same for unsigned subtraction, is it simply produces 0xFFFFFFFD as expected. No need to ask CPU to provide special SUB for signed subtraction.
To say that second operand is inversed by 2's complement and by this deduce the conclusion that CPU implements 2's complement in SUB is unfair. Because borrowing from higher bit in subtraction happens to be the same as 2's complement inversing and SUB is also used for unsigned subtraction, no need to involve the concept of 2's complememt in SUB at all.
The following disassembly illustrate the fact that signed subtraction use same SUB as unsigned.
//int32_3 = -3;
010B2365 mov dword ptr [int32_3],0FFFFFFFDh
//int32_1 = 0, int32_2 = 3;
010B236C mov dword ptr [int32_1],0
010B2373 mov dword ptr [int32_2],3
//uint32_1 = 0, uint32_2 = 3;
010B237A mov dword ptr [uint32_1],0
010B2384 mov dword ptr [uint32_2],3
//int32_3 = int32_1 - int32_2;
010B238E mov eax,dword ptr [int32_1]
010B2391 sub eax,dword ptr [int32_2]
010B2394 mov dword ptr [int32_3],eax
//uint32_3 = uint32_1 - uint32_2;
010B2397 mov eax,dword ptr [uint32_1]
010B239D sub eax,dword ptr [uint32_2]
010B23A3 mov dword ptr [uint32_3],eax
The CPU preserve additional imformation in CF and OF flags for further instructions that use result of SUB in different ways depending on the variable type that is assigned the result.
The following disassembly illustrate how compiler generates different instructions for signed compare and unsigned compare. Notice cmp
includes a internal sub
, and jle
is based on OF flag and jbe
is based on CF flag.
//if (int32_3 > 1) int32_3 = 0;
010B23A9 cmp dword ptr [int32_3],1
010B23AD jle main+76h (010B23B6h)
010B23AF mov dword ptr [int32_3],0
//if (uint32_3 > 1) uint32_3 = 0;
010B23B6 cmp dword ptr [uint32_3],1
010B23BD jbe main+89h (010B23C9h)
010B23BF mov dword ptr [uint32_3],0
It is the OF gives away the fact that CPU implements 2's complement, because the way the OF is set is when middle binary number 0x10000000 or 0x0FFFFFFF is exceeded. And 2's complement representation maps 0x10000000 to -268435456 and 0x0FFFFFFF to 268435455, which are the upper and lower limit of 32bit integer. So this OF flag is designed specifically for 2's complement, because other representation might choose to map other binary numbers to the upper and lower limit.
To conclude:
1. Compiler differentiates signed and unsigned arithmetics by implementing corresponding representations(mapping) and by generating instructions that the result of which comply with compiler's representation of signed and unsigned integer. 2. Compiler implements 2's complement representation and CPU also implement it to support compiler in generating arithmetic instructions that the result of which comply with 2's complement representatiom.