2

My function needs to return a string and it builds it using indexing. Ideally I'd like to use that return value in printf(). I understand that given my return type, I need to return a char string pointer, but I can't make reg a string pointer because I need to access individual elements. Also, I know I'd need to malloc() it, but I don't know how I'd free up that memory if it's used as an argument to printf.

Here's my code:

char * printRegister(int num) {
    char reg[] = "$error";
    memset(reg, 0, sizeof(reg));
    switch(num)
    {
        case 0:
            strcpy(reg, "$zero");
            break;
        case 1:
            strcpy(reg, "$at");
            break;
        case 2:
        case 3:
            reg[0] = '$';
            reg[1] = 'v';
            reg[2] = num - 2 + '0';
            reg[3] = '\0';
            break;
        case 4:
        case 5:
        case 6:
        case 7:
            reg[0] = '$';
            reg[1] = 'a';
            reg[2] = num - 4 + '0';
            reg[3] = '\0';
            break;
        case 8:
        case 9:
        case 10:
        case 11:
        case 12:
        case 13:
        case 14:
        case 15:
            reg[0] = '$';
            reg[1] = 't';
            reg[2] = num - 8 + '0';
            reg[3] = '\0';
            break;
        case 16:
        case 17:
        case 18:
        case 19:
        case 20:
        case 21:
        case 22:
        case 23:
            reg[0] = '$';
            reg[1] = 's';
            reg[2] = num - 16 + '0';
            reg[3] = '\0';
            break;
        case 24:
        case 25:
            reg[0] = '$';
            reg[1] = 't';
            reg[2] = num - 16 + '0';
            reg[3] = '\0';
            break;
        case 26:
        case 27:
            reg[0] = '$';
            reg[1] = 'k';
            reg[2] = num - 26 + '0';
            reg[3] = '\0';
            break;
        case 28:
            strcpy(reg, "$gp");
            break;
        case 29:
            strcpy(reg, "$sp");
            break;
        case 30:
            strcpy(reg, "$fp");
            break;
        case 31:
            strcpy(reg, "$ra");
            break;
        default:
            strcpy(reg, "error");
            break;
    }
    return reg;
}

Is there a way to do this?

  • Have you considered making `void printRegister(int num, void (*fn)(char *)) {`? You could pass the code that uses it as a callback function, declare the string on the stack, and still maintain separation of concerns. At the end, you'd just `fn(reg);` instead of `return reg;` – Patrick Roberts Apr 01 '19 at 22:13
  • @PatrickRoberts Interesting, this is new to me. So if the function that uses it is `printf("rs: %s\n", printRegister(rs));` then how would I pass that as an argument when `printRegister()` uses the that printf statement itself as an argument? – Alessandro Lorusso Apr 01 '19 at 22:26
  • you could change `return reg;` to `return strdup(reg);`. Note that strdup is not a standard library function, but it is commonly available in most distributions. – bruceg Apr 01 '19 at 22:40

4 Answers4

3

How do I return a string from a function that's meant to be used in printf in C?

With C99 or later, use a compound literal for temporary string storage.

First, rewrite printRegister() to accept a char * buffer.

#define  printRegister_N 7
// char * printRegister(int num) {
char * printRegister(char *reg, int num) {
  // char reg[] = "$error";
  // memset(reg, 0, sizeof(reg));
  memset(reg, 0, printRegister_N);  // not really needed anymore with compound literal
  ...
  return reg;
}

Form a macro that calls printRegister() with a compound literal.

//                                   v---------------------------v  compound literal
#define PRINT_REG(num) printRegister((char [printRegister_N]){ 0 }, (num))

Then call PRINT_REG(num). The returned char* (which is a pointer to the compound literal) is valid until the end of the block. No *alloc(), free() needed.

printf("1st:%s  2nd:%s\n", PRINT_REG(0), PRINT_REG(1));

Code still use the usual adornments to "%s" like "%-7s", "%.*s", etc.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
2

The current code has undefined behavior because you return a pointer to an object with automatic storage that will go out of scope when the function returns.

There are several ways to fix this problem:

  • you could give reg static storage class. Returning a pointer to reg would be OK but the contents will be overwritten by subsequent calls to printRegister, so you cannot use printRegister multiple times as arguments to a single printf call.

  • you could return an allocated copy of reg, but the caller will need to free this memory block to avoid memory leaks. This makes it cumbersome for your purpose.

  • you could change the prototype for printRegister to take a pointer to the destination array and return that after storing the textual representation of the register name. Note that you would need separate arrays for multiple calls to printRegister in the same printf call.

  • since there are only 33 different possible strings, you could return string constants. This solution is simple and efficient, but is advisable to change the return type to const char *.

Here is an implementation of the last approach:

const char *printRegister(int num) {
    static const char * const regname[] = {
        "$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3",
        "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7",
        "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7",
        "$t8", "$t9", "$k0", "$k1", "$gp", "$sp", "$fp", "$ra",
    };
    return (num >= 0 && num < 32) ? regname[num] : "error";
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 1
    In hindsight I'd have used your last approach as well, since string literals have static storage and there's no risk of overwriting the buffer unless you do something really stupid like cast the reference to non-constant. Nice answer :) – Patrick Roberts Apr 01 '19 at 22:49
  • I think this solution is the best for my situation – Alessandro Lorusso Apr 02 '19 at 16:46
0

Try strdup(), it will allocate the memory for you and copy any string you want to it.

also any block of memory that has been dynamically allocated can be freed with free().

Heres an example:

#include <string.h> //note: don't include "string", instead include string.h 
#include <stdlib.h> //note: this header defines free,

int main()
{
    char *data = strdup("the return value of strdup is will be a string containing this");

    /* use dynamically allocated string... */



    free(data); /* free up the memory. */
}
  • 1
    `strdup` is non-standard. More precisely, it's defined by POSIX but not by ISO C. And you need to check whether the call succeeded or failed (it returns a null pointer if the allocation failed). If you want your code to be portable, it's easy enough to do what `strdup` does by calling `malloc` and `strcpy`. – Keith Thompson Apr 01 '19 at 22:50
0

By declaring a helper function that takes your string argument

void myprint(char *str) {
    printf("rs: %s\n", str);
}

You can change the implementation of printRegister() to

void printRegister(int num, void (*fn)(char*)) {
    ...
    fn(reg); // instead of return reg;
}

Then call

printRegister(rs, myprint);

This circumvents the need for dynamically allocated memory by reversing the roles of caller and callee; the caller of printRegister() now provides a callback which can accept the temporary stack variable and do something with it.

Try it online!

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153