简体   繁体   中英

Can I modify the return data type from int to int8_t or int16_t?

I am using MIPS32 and coding in C.

currently many functions in my code return 'int' data type.

Since my development is on resource constrained hardware (even bytes matter) and the return values are just error codes (don't exceed 255), I am planning to shrink the return type either as int8_t or as int16_t.

What I am trying to achieve is to reduce the stack/memory usage of caller.

Before I attempt, Will this result in stack/memory usage reduction in the caller? or

Since I have heard of memory alignment (mostly as 4 bytes) & don't know much, will that play a spoil sport here?

Example

int caller(){
    int8_t status;
    status = callee();

}

int8_t callee() {
    ...
    return -1;
}

In the example above, does the status identifier declaration as int8_t or int16_t or int matters in mips32?

This will create absolutely no change when it comes to the call stack, an example of the MIPS call stack can be found here. https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf

$31 $ra The Return Address in a subroutine call.

Below that is an image and you will see the return address which is a full register, in your case using a 32bit machine your register will be size of 32bits and there is no changing that.

I do have to ask though, what are you doing that requires MIPS? Generally speaking that is a language used for teaching purposes and doesn't have much in the way of real world practical uses since it has many many flaws. As an example this concept of a return address does not exist with modern assemblies like X86 where the stack pointer will contain all that information.

EDIT: As pointed out by people below I have been a bit unfair. Technically these address also exist.

$2-$3 $v0-$v1 These registers contain the Returned Value of a subroutine; if the value is 1 word only $v0 is significant.

Again though they have a set size and from the perspective of the call stack they are using one full register. Theoretically I believe MIPS has ways to store 4 bytes inside of one register but I am unsure on this. More importantly though with the way MIPS works these return registers can ONLY be used if the call is one function deep. If you call a function within a function this concept falls apart and the return address becomes required hence why I just showed that one origonally.

First of all, "don't exceed 255" means you should be using uint not int .

When manually optimizing code for size, you should be using the uint_leastn_t types. These types allow the compiler to pick the smallest possible type necessary for the code to work, which is at least n bytes wide.

In your case this would be uint_least8_t . Though of course if the compiler always picks a 32 bit type, because that is what is required for aligned access, then the only thing you have gained by replacing int is better portability.

On MIPS32 the first four function parameters (integers or pointers; for simplicity I'm not considering 64-bit ints, floats or structs) arrive in registers a0 through a3. The rest goes on the stack, with each machine word of stack memory holding just one parameter. So, in terms of passing the error codes there will be no difference.

If you have to store error codes in local (automatic) variables, a lot will depend on the code. However, MIPS has plenty of registers and chances are there will be a register available for an error code and hence no stack space for it will be needed.

If you have global variables holding error codes, then definitely there will be a difference between using differently sized types.

Going back to the stack, you should note that there are several other things at play...

First, the stack must be aligned. This is worsened by the fact that modern compilers tend to align the stack pointer not on a multiple of the machine word, but on a multiple of two machine words. So, if you're considering just one error code, it's quite likely that any gains will be undone by the compiler padding the local variables on the stack to make their cumulative size a multiple of two machine words.

Second, the stack pointer is typically decremented by the size of the local and temporary variables just once at the entry of the function (and the reverse is done just once on exit). This means that in some places in the function there may be some unused stack space, which is reserved only to be used in other places of the function. So, calls (especially deep recursive calls) from some places of the function will be unduly wasting stack space.

Third, those four parameters that arrive in a0 through a3 are required by the ABI to have stack memory associated with them, so they can be stored there and addressed by pointers in functions like printf (recall stdarg.h's va_list, va_start(), va_arg(), etc). So, many calls may be wasting those 16 bytes of stack space as well.

Another thing you might want to consider is that when a function returns 8-bit or 16-bit integer types, the caller will need to sign-extend (or zero-extend) those 8/16 bits to the full machine word size (32 bits), meaning that the caller will have to use additional instructions like seb, seh and andi. So, these may affect code size negatively.

Ultimately, it depends a lot on your code and on your compiler. You can measure the stack usage using both types and using different optimization options of the compiler and choose the best. You can also experiment with restructuring your code to avoid calls or to make it easier for the compiler to optimize it (eg static functions help as the compiler may deviate from the ABI when calling them and more effectively optimize them and passing and returning values to and from them). And this is really what you should do, try different things and choose what you like the best.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM