This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

How to tread the different memory spaces?

Hello,

Suppose you declare an array in a certain memory space, for instance

unsigned char xdata SendBuffer[ 20 ];
.
Is it allowed to just prototype the callees with
byte *SendBuffer
and dereference it as
TempVal = *( SendBuffer + Counter );
?
Or should it be casted to the correct memory space, like
TempValue = *( (unsigned char xdata*)SendBuffer + Counter );
?
In other words, is the Keil compiler 'clever' enough to see the memory space in the called functions and do the correct memory space conversion itself?
That means, if I declare my SendBuffer in
idata
, will it also work correctly with the same prototype for the callees?

I've run the PC simulator and it seems it works fine, but I just want confirmation.

Rgds,

Geert

  • Keil C51 uses a "tagged" representation for a pointer to an unspecified memory space. Let's call that a "generic" pointer. The generic pointer has two bytes of address, plus one byte of "tag" that tells the code whether the address is in code space, xdata space, data space, and so on.

    If you declare the pointer with a particular memory space specified, the pointer will only take up two bytes, and you code gets a little more efficient.

    
    char* SendBuf;  // 3-byte pointer
    char xdata* SendBuf; // 2-byte pointer
    
    

    The compiler converts between the two pointer representations as needed. If you pass a 2-byte pointer to a routine that's declared to take a 3-byte pointer, the compiler can add the third byte as necessary. You can force a generic pointer into a memory-specific pointer with a cast, but you run the risk of having a run-time error if the generic pointer didn't really point to the right memory location.

    Routines with prototypes that declare formal arguments as generic pointers will pass the three-byte version; routines that declare their parameters as specific types will pass two-byte pointers.

    
    // passes 3-byte pointer in R1-R3
    void MyFunc (char* SendBuf);
    
    // can't pass 2 generic pointers in registers
    void MyFunc2 (char* dst, char* src);
    
    // passes 2-byte pointer in R6/R7
    void MyFunc (char xdata* SendBuf);
    
    // passes pointers in R4/R5 and R6/R7
    void MyFunc2 (char xdata* dst, char xdata* src);
    
    

    If you know ahead of time that a particular routine only references a particular memory space -- say, for example, you have a MallocXdata() routine -- then you'll get more efficient code by using specific pointers. The code will work either way, though. It's an efficiency question, not a correctness one.

    Note that since the _caller_ is the one that has to line up parameters of the appropriate size in the appropriate registers, only the function prototype matters. Even if the compiler could tell by looking at the function body that a routine only accessed a certain space, there's no way for a separately-compiled translation unit to know that.

    If you write your own routines in assembler to be called from C, then you have to be careful about the sort of pointer your assembler code will accept. You'll either have to prototype the routine as a specific pointer, or write code in your routine to interpret the generic pointers.

    See the C51 User's Manual, "Pointers", page 106.

  • "you'll get more efficient code by using specific pointers"

    That depends upon your definition of "efficient"

    If you use Generic Pointers, the compiler will use Library calls to handle them; if you use memory-specific pointers, the compiler can put the code in-line.

    Thus, using memory-specific pointers can actually increase your code size - although execution speed should be improved.

    As I said, it all depends on what you mean by, "efficient"


  • This is why the compiler has an option for the optimizer to favor speed or to favor space :)