The Solution

The .data and .text commands can take a label name after them—this names a new segment. We'll define a new segment called zp (for "zero page") and have our zero-page variables be placed there. We can't actually use the default origin of $0000 here either, though, because the Commodore 64 reserves memory locations 0 and 1 to control its memory mappers:

.data zp
.org $0002

Now, actually, the rest of the zero page is reserved too: locations $02-$8F are used by the BASIC interpreter, and locations $90-$FF are used by the KERNAL. We don't need the BASIC interpreter, though, so we can back up all of $02-$8F at the start of our program and restore it all when we're done.

In fact, since we're disablng BASIC, we can actually also swap out its ROM entirely and get a contiguous block of RAM from $0002 to $CFFF:

.scope
        ; Cache BASIC zero page at top of available RAM
        ldx     #$8e
*       lda     $01, x
        sta     $cf81, x
        dex
        bne     -

        ; Swap out the BASIC ROM for RAM
        lda     $01
        and     #$fe
        ora     #$06
        sta     $01

        ; Run the real program
        jsr     _main

        ; Restore BASIC ROM
        lda     $01
        ora     #$07
        sta     $01

        ; Restore BASIC zero page
        ldx     #$8e
*       lda     $cf81, x
        sta     $01, x
        dex
        bne     -

        ; Back to BASIC
        rts

_main:
        ; _main points at the start of the real program,
        ; which is actually outside of this scope
.scend

Our print'str routine is then rewritten to declare and use a zero-page variable, like so:

; PRINTSTR routine.  Accumulator stores the low byte of the address,
; X register stores the high byte.  Destroys the values of $10 and
; $11.

.scope
.data zp
.space _ptr 2
.text
printstr:
        sta _ptr
        stx _ptr+1
        ldy #$00
_lp:    lda (_ptr),y
        beq _done
        jsr chrout
        iny
        bne _lp
_done:  rts
.scend

Also, we ought to put in an extra check to make sure our zero-page allocations don't overflow, either:

.data zp
.checkpc $80

The final source file is hello7.oph.