INT 21h AH=49h returns a block to DOS. ES is the segment INT 21h AH=48h returned, the block itself; DOS finds the MCB in the paragraph below and marks the block free (owner 0). Free neighbours are merged into one block on a later call.

Memory a program forgets to free is not lost: when it exits with INT 21h AH=4Ch, DOS frees every block owned by its PSP. Freeing matters to a program that keeps running: one that allocates and frees buffers as it goes, one that runs other programs, a resident program trimming itself.

DOS checks very little. It only looks for "M" or "Z" at ES - 1:0. Freeing a block twice, or a block another program owns, succeeds without complaint, and the trouble shows up later. An ES that does not point just past an MCB gives error 9:

prints: free the block: ok / free CS+1, not a block: error 9
MOV AH, 4Ah ; first make room (ES = our PSP)
MOV BX, 1000h
INT 21h
MOV AH, 48h ; DOS: allocate 1 KB
MOV BX, 64 ; 64 paragraphs
INT 21h
JC DONE1
MOV ES, AX ; ES = the block
MOV DX, OFFSET FREEM1
MOV AH, 09h
INT 21h
MOV AH, 49h ; DOS: free the block at ES
INT 21h
CALL RESULT ; ok
MOV DX, OFFSET BOGUSM1
MOV AH, 09h
INT 21h
MOV AX, CS
INC AX
MOV ES, AX ; "MCB" at CS: that is our PSP, CDh 20h...
MOV AH, 49h
INT 21h
CALL RESULT ; error 9
PUSH CS
POP ES
DONE1:
FREEM1 DB "free the block: $"
BOGUSM1 DB "free CS+1, not a block: $"
RESULT PROC NEAR ; print "ok", or "error" and AX when CF = 1
PUSHF ; keeps AX and CF for the caller
PUSH AX
MOV DX, OFFSET OKMSG
JNC RESULT1
MOV DX, OFFSET ERRMSG
MOV AH, 09h
INT 21h
POP AX
PUSH AX
CALL PDEC ; the error code
MOV DX, OFFSET CRLF
RESULT1:
MOV AH, 09h
INT 21h
POP AX
POPF
RET
RESULT ENDP
OKMSG DB "ok", 13, 10, "$"
ERRMSG DB "error $"
PDEC PROC NEAR ; print AX in decimal (changes AX BX CX DX)
MOV BX, 10
XOR CX, CX ; digits on the stack
PDEC1:
XOR DX, DX
DIV BX ; AX = AX / 10, DX = the last digit
PUSH DX
INC CX
OR AX, AX
JNZ PDEC1
PDEC2:
POP DX ; the digits come back first to last
ADD DL, "0"
MOV AH, 02h
INT 21h
LOOP PDEC2
RET
PDEC ENDP
CRLF DB 13, 10, "$"

Freeing the environment

Each program gets its own copy of the environment, in a block whose segment is at PSP:2Ch. A program that stays resident frees it before INT 21h AH=31h to save memory, and often writes 0 to PSP:2Ch afterwards so that nothing follows a stale pointer:

MOV ES, DS:[2Ch] ; DS = our PSP: the environment's segment
MOV AH, 49h ; DOS: free it
INT 21h