Q:

x86_64 assembly %rsp vs %esp

I have been playing with assembly recently, and I came across a strange bug in my program. I have found that if I modify %rsp by doing 64-bit math, then everything works fine, but if I modify %esp by the same amount, except with 32-bit math, I get a segmentation fault. I tried printing out both %esp and %rsp, and they are the same every time I run.
QUESTION: Why does it matter whether I do 64-bit math or 32-bit math when the whole register is only using 32 bits?
.cstring
    _format: .asciz "%d\n"

.text
.globl _main
_main:
    # program setup
    pushq   %rbp
    movq    %rsp, %rbp

    # program - 16 byte aligned at this point
    # print stack pointer memory
    movq    %rsp, %rax
    call    bob            # prints the same value as the next call to bob
    xorq    %rax, %rax
    movl    %esp, %eax
    call    bob            # prints the same value as previous call to bob

    # this code breaks
    subl    $16, %esp      # bug here if I use this (32 bit math)
    subq    $16, %rsp      # works fine if I use this (64 bit math)
    call    bob
    addq    $16, %rsp

    # program cleanup
    movq    %rbp, %rsp
    popq    %rbp
    ret

# assumes 16 byte aligned when called. Prints %rax
 bob:
    subq    $8, %rsp
    movq    %rax, %rsi
    lea     _format(%rip), %rdi
    call    _printf
    addq    $8, %rsp
    ret

A:

In x86_64, addresses are 64 bits, so how can you expect to do 32-bit math on it and still working fine? Moreover most 32-bit operations on x86_64 zero out the top 32 bits, render the address invalid

