0

Consider code c.c

void f(unsigned char *a, long long *b)
{
    *b = (long long)*a;
}

Compile it with

$ gcc -Og -S c.c

where

$ gcc --version
gcc (MinGW-W64 x86_64-posix-seh, built by Brecht Sanders) 10.2.0

and my machine is a 64-bit Windows 10.

Among other lines, I get the assembly code as follows

01 movzbl  (%rcx), %eax
02 movq    %rax, (%rdx)

My question is: Why isn't the first line written in this way

01 movzbq  (%rcx), %rax

What if the higher 32 bits of %rax originally had some non-zero bits, and were not set to zero after movzbl (%rcx), %eax? Won't these non-zero bits (if any) get copied to (%rdx) by movq %rax, (%rdx)?

A follow-up question is: Even the above concern is unneeded, still, why isn't the first line written in this way

01 movzbq  (%rcx), %rax

i.e. governed by which rule the translation from C to assembly code is done in the given way?

(I have some knowledge with C but am new to assembly code.)

Update: Would like to make some clarification after I read the comments (appreciate all of them). A comment says the function is unnecessary, and I may just do that assignment. That is right. As another comment rightly puts, this is a pared-down example. What I want to understand is simply why the C-to-assembly translation happens this way when casting a unsigned char to long long.

aafulei
  • 2,085
  • 12
  • 27
  • 6
    Zeroing the top 32 bits of `rax` [happens anyway](https://stackoverflow.com/q/11177137/555045) – harold Feb 08 '21 at 11:19
  • 2
    `(long long)*a;` probably doesn't do what you think it does. All it does is cast the `unsigned char` value from dereferencing `unsigned char *a` to a `long long` value. You're taking the ***single*** byte that `a` points to and assigning it to the 8 bytes that `b` points to. If you're trying to treat the `usigned char *a` as a pointer to a chunk of memory you want to treat as a full `long long` value, that's almost certainly [a strict aliasing violation](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) and undefined behavior. – Andrew Henle Feb 08 '21 at 11:27
  • 3
    @AndrewHenle: I think you have misunderstood the question. – TonyK Feb 08 '21 at 11:34
  • @harold, your link explains that zero-extension only happens with a 32-bit source. 8- and 16-bit sources (like here) are not zero-extended. Hence the requirement for two instructions. – TonyK Feb 08 '21 at 11:36
  • @TonyK Maybe. But the cast in `*b = (long long)*a;` is completely unnecessary, and the title is *converting* an `unsigned char` value address to a `long long` and not just assigning an `unsigned char` to a `long long`. If all the questioner wants to do is the assign a single `unsigned char` value to a `long long`, the entire function is not needed - ***just do that assignment***. – Andrew Henle Feb 08 '21 at 11:40
  • @AndrewHenle, I expect that this is a pared-down example of more complicated code, that exhibits the same puzzling behaviour. – TonyK Feb 08 '21 at 11:44
  • 2
    For the second part of the question: The encoding of `movzbl (%rcx), %eax` is one byte shorter than `movzbq (%rcx), %rax` since it doesn't need the REX prefix. – user786653 Feb 08 '21 at 12:03
  • @user786653 Good point! But is this (one encoding is one byte shorter than the other) the reason for the preference of one over another? – aafulei Feb 08 '21 at 12:45
  • @aafulei: Yes, that is the reason. – Nate Eldredge Feb 08 '21 at 14:24
  • Does this answer your question? [Why do x86-64 instructions on 32-bit registers zero the upper part of the full 64-bit register?](https://stackoverflow.com/questions/11177137/why-do-x86-64-instructions-on-32-bit-registers-zero-the-upper-part-of-the-full-6) – Nate Eldredge Feb 08 '21 at 14:24
  • 2
    You might find it interesting to change `unsigned char` to `signed char`. You should then see `movszbq (%rcx), %rax` with the 64-bit destination, because now the high bits of `%rax` need to be set to the sign bit of the byte `*a` and not unconditionally to 0. In this case the longer encoding can't be avoided. – Nate Eldredge Feb 08 '21 at 14:28

1 Answers1

3

movzbl 1) zero extends to 32 bit (‘z’), and 2) zero extends to 64 bit (32 bit operands are implicitly “zero extended”) for %eax.

32-bit instruction movzbl's encoding is shorter than the 64-bit instruction movzbq’s encoding.

ericzma
  • 763
  • 3
  • 9
  • 23