So I'm tasked with writing a subroutine to access a certain element of an array. When the subroutine is called, the pointer that points to it is [bp+1]. How would I make it so I could access say the 10th element of this array in the sub routine?

Recommended Answers

All 5 Replies

Since you've specified that the pointer is in [BP+1], I will assume that you are using 16-bit registers. [BP+1] does however sound a bit odd, given that arguments in a 16-bit segment should be 2 bytes and therefore BP offsets should always be even. nevertheless, I'll use your address. First you need to put the pointer into a register. Let's use SI for the example. It's usually available and even if not, you can always push the old value. I will also assume that the array offset arrives in BX and that you want to return a 16-bit value. So here goes:

push SI    //Save old SI
   mov SI,[BP+1]  //Get the pointer into SI
   push BX    //Save BX - only necessary if the caller needs this unchanged
   shl BX, 1  //Here is the assumption that each element is 2 bytes
   mov AX, [SI][BX] //Move contents into AX from address in SI offset by BX bytes
   pop BX
   pop SI
   ret    //Return with result in AX

I've left the proc definition to you. Note that if you define your arguments in a proc definition, then you never need to do all the BP counting to find arguments since you can name the arguments and then refer to them by name. As long as you load them in the right registers and/or push them in the correct order, the assembler will handle all the rest.Something like "USES si bx, array:WORD, index:bx" in the proc statement should eliminate the Push/Pop stuff and the BP arithmetic and let you reference the array pointer as [array] and BX as index. Good luck.

By the way, if this is 32 bit code, then be sure to use the 32 bit register names (ESI and EBX) for the addressing. AX or EAX depends upon the element size of the array. Even if this is a 16-bit segment, if you are on a 32 or 64 bit machine, you can use the 32 bit addressing registers, which actually has some advantages. For example, you can put a scaling factor on the index register and avoid the shift operation on BX (as well as the push and pop). So instead of:

push BX
   shl BX, 1
   mov AX, [SI][BX]
   pop BX

Use this:

mov AX, [ESI][EBX*2]

It's faster, cleaner and more intuitive, as well as being easier to code. The scale factor can be either 1, 2, 4, or 8 on 32-bit machines. 64-bit machines may add more scaling factor values, of that I'm not sure. (1 is obviously degenerate and is the same as specifying no scaling value). The scaling value cannot be a variable, it is coded as part of the binary value of the instruction.

BP+1 does sound odd due to the fact that this would refer only
to the second byte of the subroutines return address, and because
WORDs are pushed and popped off the stack, thus the indexes would
normally be an even value when indexing into the stack.
After setting up a stack frame the first parameter should be at [BP+2].

MOV BX, [BP+n] ; load pointer to array
; copy 10th element of array into AL
MOV AL, [BX+10] ; if 8-bit array
MOV SI, 10 ; index of 10
SHL SI, 1 ; multiply by 2
MOV AX, [BX+SI] ; if 16-bit array
...

@NotNull, The only criticism I would have is more of a caveat about hard coding offsets into instructions. (i.e. MOV AL, [BX+10]). This is what you SHOULD do in cases where you are accessing a a predefined table and you want a specific byte, lets say an entry from a header that always contains a flag word at 10th byte. However, in the case of accessing an array, you should always put the offset in an index register, as you did in the second case (MOV AX, [BX+SI]). In terms of usage, BX and SI are interchangeable - either can be the base and either the index. You just can't use 2 indexes (DI+SI) or 2 bases (BP+BX). I still would be tempted to use 32-bit register format unless I absolutely had to use 16 bit registers. More combinations of registers are legal and scaling is built into the instructions. There are those rare cases where code is being written for a 16-bit CPU, however, in truth, it's been years since I've even seen one, let alone coded for one.. Taking these suggestions, and assuming bp+2 is the pointer, and bp+4 is the index. Your code fragment then becomes:

1. MOVZX EBX, word ptr [BP+2] ; load pointer to array (setting high bits to zero)
2. ; copy 10th element of array into AL
3: MOVZX ESI,word ptr [BP+4] ; Set the index value
4. MOV AL, [EBX+ESI] ; if 8-bit array
5. MOV AX, [EBX][ESI*2] ; if 16-bit array
...

Note that there is no way that the CPU can know if you want to load a byte, a word, or a dword into EBX/ESI. You need to tell it the size using word ptr. If you have pushed 32-bit arguments, then Zero extension and the ptr operators would not be needed, thus, for the 32-bit address case (note also the change in the bp offsets to accommodate 32-bit code:

1. MOV EBX,[BP+4] ; load pointer to array
2. ; copy 10th element of array into AL
3: MOV ESI,[BP+8] ; Set the index value
4. MOV AL, [EBX+ESI] ; if 8-bit array
5. MOV AX, [EBX][ESI*2] ; if 16-bit array
; What the heck, lets make the example complete and add the 32-bit array case
6. MOV EAX, [EBX][ESI*4] ; if 32-bit array
...

It's up to you to be sure that the effective address is within the array bounds (indeed, within the segment limits!)

Nothing bad intended I was only emphasizing what you already said:
"Since you've specified that the pointer is in [BP+1], I will assume that you are using 16-bit registers. [BP+1] does however sound a bit odd, given that arguments in a 16-bit segment should be 2 bytes and therefore BP offsets should always be even."

NotNull, "BP+1 does sound odd", it really does sound like an odd index to use
into the parameters of a subroutine.

"However, in the case of accessing an array, you should always put the offset in an index register"
I did not intend to imply that using index registers is obsolete, eg:

MOV SI, 10
MOV AL, [BX+SI]

works just fine.

I was only commenting on the accessing of parameters placed on the stack
before a subroutine call, no criticism intended, sorry...

A stack frame is set up in this manner:

PUSH BP
MOV BP, SP
MOV AX, [BP+4] ; first parameter

The stack will appear thus after the stack frame:
[First Parameter] <--- SP+4
[Return Address ] <--- SP+2
[Old Value of BP] <--- SP
I said previously that the first parameter will be at BP+2 after setting
up a stack frame when in fact it would be at BP+4, sorry about that.

Hope that helps, good day.

NotNull,

No criticism taken! That's one of the wonderful things about these forums - when they work, they work really well as a genuine exchange of ideas and techniques. I also find it interesting that I also forgot to include the offset for the BP on the stack in my arithmetic - I've been doing that on and off for years. It really goes to show you how useful the "proc" directive can be when defining functions, since it lets you reference all the parameters by name and makes the assembler do all the arithmetic! It's definitely worth the work to learn how to use it. I hope that the original poster has found this exchange useful.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.