Yes, a buffer is just an array, which in assembly is a sequence of bytes.
You have 3 main options for allocating it, exactly like in C:
static storage: like C static char buf[100];
section .bss ; this might not be proper MASM syntax
my_buffer: db 100 dup(?) ; but this is definitely MASM
Putting a :
between the label name and the db
makes it just a plain label, like NASM, not a MASM "variable" with an implied operand-size. (If MASM lets you do that in a .data / .bss section. It might not.)
100 dup
means to repeat the next thing 100 times. ?
means uninitialized storage. It's actually going to be zeroed in a program that runs under an OS like Windows, because it can't let programs see stale data left over from kernel data or other processes on the same machine. So 100 dup(0)
would also work and maybe be a better description of what you want, especially if your code ever reads any of these bytes without writing first.
dynamic storage: call malloc
, or invoke an OS function directly like mmap
or VirtualAlloc
. You can return a pointer to it from the function that allocated it.
automatic storage (on the stack): like a C local variable.
Deallocated automatically when the allocating function returns. Very cheap and easy, use this for scratch buffers unless you know they need to be multiple megabytes.
The easiest way to deal with buffers is to accept a pointer to an already-allocated buffer, and give your caller the choice of what buffer to pass.
For example, a function that uppercases ASCII letters could just take a src and dst pointer. If you want it to operate in-place, you can just pass the same pointer for input and output, if it's written to support that. It doesn't have to care about memory management, it just operates between two buffers.
A function like C strdup
makes a new copy of a string, and that only makes sense with dynamic storage. Copying the string into a static buffer and returning that wouldn't work well, because there's only one instance of that static buffer. The next call to it would overwrite the old contents.
Allocating a buffer on the stack:
A variable-size buffer on the stack is no problem; you just need a way to clean up the stack afterwards. Making a stack-frame with EBP / RBP is an easy way to do that. Consider this example function that allocates a buffer as large as needed, and uses it to hold the output from a string-reverse function so it can pass it to a print
function. You can see what compilers do in this case.
void string_reverse(char *d, const char*s, int len);
void print(const char*s, int len); // modify this to an fwrite or whatever.
void print_reversed(const char *s, int len) {
char buf[len];
string_reverse(buf, s, len);
print(buf, len);
}
This is what you might do by hand, if string_reverse
doesn't need 16-byte stack alignment and it doesn't clobber its stack arg. (The ABI / calling convention doesn't guarantee either of those things, so we're taking advantage of special knowledge of the function we're calling to simplify print_reversed
.)
; MSVC __fastcall convention
; args: ecx, edx (const char *string, size_t length)
print_reversed PROC
push ebp
mov ebp, esp ; make a stack frame
sub esp, edx ; reserve space for a buffer
and esp, -16 ; and realign the stack
; allocate buf[length] on the stack, address = esp
; mov eax, esp ; if you want to copy it somewhere
;sub esp, 12 ; ensure 16-byte stack alignment before CALL
push edx ; 3rd arg and later args go on the stack
mov edx, ecx ; 2nd arg = string
lea ecx, [esp+4] ; 1st arg = output buffer = what we allocated. (PUSH offset ESP by 4, LEA corrects for that)
call string_reverse ; (dst=buf (ECX), src=string (EDX), length=length (stack))
; clean up the stack after the call and set up args for print
pop edx ; assuming string_reverse doesn't modify its stack arg
mov ecx, esp ; esp is once again pointing to our buffer
call print ; print(ECX=buf, EDX=length)
; lea esp, [ebp-8] ; if you needed to push stuff after EBP, restore this way
; pop ebx / pop esi / pop ebp
leave ; mov esp, ebp / pop ebp to clean up the stack frame
ret
ENDP
This is how most C compilers implement alloca
or C99 variable-length arrays.
sub esp, ecx
/and esp, -16
. It's the best option for scratch buffers for local use, or for passing to other functions. – Mydriatic