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-$7F are used by the BASIC interpreter, and locations $80-$FF are used by the KERNAL. We don't need the BASIC interpreter, though, so we can back up all of $02-$7F at the start of our program and restore it all when we're done:

.scope
        ; Cache BASIC's zero page at top of available RAM.
        ldx #$7E
*       lda $01, x
        sta $CF81, x
        dex
        bne -

        jsr _main

        ; Restore BASIC's zero page and return control.

        ldx #$7E
*       lda $CF81, x
        sta $01, x
        dex
        bne -
        rts

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

The new, improved header file is c64-2.oph.

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

That concludes our tour. The final source file is tutor7.oph.