Char* versus char array [Solved]

I am a little puzzled why this code does not work. I am reading the MAC address of the device, storing it as an array of bytes/uint8_t in eeprom and then converting it to a string/char array to use when including in an event message. In the code snippet below, if I declare macAddr as a char array i.e. char macAddr[] = “00:00:00:00:00:00”; Then this works and end of loop macAddr is correct whereas if declare as char* macAddr; it returns the initialised value at the end of loop. Can anyone explain this?

char* macAddr;
uint8_t macA[6];

void setup()
{
    macAddr = "00:00:00:00:00:00";
    for (int i = 0; i < 6; i++) {macA[i] = 0x0;}
}

void loop()
{
    Serial.printlnf("macAddr [start of loop]: %s", macAddr);
    getMACaddress();
    Serial.printlnf("macAddr [end of loop] : %s", macAddr);
    delay(1000);    
}
// helper function to get mac address into a string macAddr
void getMACaddress()
{
    if ((macA[0] + macA[1] + macA[2] + macA[3] + macA[4] + macA[5]) == 0)   //check if eeprom mac address not set
    {
        byte mac[6];                                                        //define and set to zeros
        WiFi.macAddress(mac);                                               //read from wifi module else use stored mac address
        if ((mac[0] + mac[1] + mac[2] + mac[3] + mac[4] + mac[5]) > 0)      //update mac address stored if not zeros
        {
            for (int i = 0; i < 6; i++) {macA[i] = mac[i];}
        }
    }
    int j = 0;
    for (int i = 0; i < 6; i++)
    {
        if (i > 0) {macAddr[j] = ':'; j++;}
        macAddr[j] = binAscii(macA[i] >> 4);
        j++;
        macAddr[j] = binAscii(macA[i] & 0x0F);
        j++;
    }
    macAddr[j] = '\n';     //null string terminator         
}
// helper function to return ascii from unsigned byte 0x0-F or ? if out of range
char binAscii(uint8_t c)
{
    if (c >= 0x0 && c <= 0x9)      return (c + '0');
    else if (c >= 0xA && c <= 0xF) return (c - 0xA + 'A');
    else                           return '?';              //if out range 0-F
}

If all you want to do is record the MAC in an HEX ASCII format, this is a simple way of doing it because sprintf() handles the byte to ascii conversion and formatting in one go:

            byte mac[6];
            char szBuf[24];

            WiFi.macAddress(mac);
            sprintf(szBuf, "MAC: %02X:%02X:%02X:%02X:%02X:%02X", 
                            mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

You can print szBuf or put to your log, etc.

When you code:

char macAddr[] = "00:00:00:00:00:00";

you are effectively coding:

char macAddr[18];
memcpy(macAddr, "00:00:00:00:00:00", 18);

The result being the variable macAddr is now a pointer to 18 bytes of storage allocated for your app.

When you code:

char* macAddr;
macAddr = "00:00:00:00:00:00";

The result is that the variable macAddr is now a pointer to storage that is constant and could easily be in flash as opposed to RAM. It is likely that your app doesn’t have write permission. I’m surprised a run-time exception wasn’t thrown when the app tried to perform a write request to what appears to me to be read-only storage.

1 Like

@kolban, that is what I thought was happening - char* macAddr = "00:00:00:00:00:00"; was creating a constant char* (read only). Is the way to stop this to declare it as volatile char* macAddr; Then do a malloc(); and then assign it an initial value?

@UMD, good point - it was the effect of char* macAddr; versus char macAddr[] I was trying to understand. Code space is a big issue which is why I would like to try to not include C library functions like sprintf() if possible.

Not quite. As @kolban already said

char* macAddr is only creating a pointer to one character (which may or may not be followed by others to build a string). It does not allocate any memory for that character (or string).
Your subsequent assignment of a string literal to that pointer was actually just a pointer assignment which transfers the address of that string literal (which most likely lives in flash) into that pointer variable. And since that string literal lives in a non-volatile memory location you won't be able to write to it (directly).
If you want to have char* macAddr refer to another string you have to "move" the pointer to a memory location where this new string lives or where you can manipulate it (RAM).

But char* macAddr = "00:00:00:00:00:00"; was not creating a const char* macAddr - it did what you wrote: "Create a char* macAddr pointer" , which is not const but was initialised to point to a non-volatile memory region.

The volatile keyword is a completely different matter and doesn't contribute to achieving your goal at all.

@armor, re sprintf() - as you are already using printlnf(), my guess is that sprintf() is being provided “for free”.

On a different matter, you said “storing it as an array of bytes/uint8_t in eeprom”. Not sure why you would want to do that if you just want to access the data within a session anyhow. I don’t see the point of storing at all - just call the API when you need it.

@UMD, simple answer to the last point is that if the device is not WiFi connected I believe that WiFi.macAddress(mac) does not work, but a user may want to enquire of the MAC address hence why I store in eeprom.

@ScruffR, OK I understand now. What would a solution look like to create a string intialised to “00:00:00:00:00:00” but still writable? In the end I am wondering if this is worth the hassle versus defining a string object.

I’d just go with a char macAddr[18] = "00:00:00:00:00:00"; as @kolban has already mentioned.

Understanding pointers, character arrays and C strings is always worth the effort - without proper grasp on the C/C++ fundamentals writing good stable code will become a gambling game :wink:

BTW, WiFi.macAddress() should also work without an active connection. The only thing required is to have the module (temporarily) on (WiFi.on()).

@armor, you said:

“if the device is not WiFi connected I believe that WiFi.macAddress(mac) does not work”

I say, as this didn’t sound right, I tested it.

As suspected, WiFi.macAddress() does return the MAC address with, or without, a WiFi connection.

Hope this helps!

2 Likes

@ScruffR, @UMD, Thanks both - I will do a bit of simplification and make the MAC address fetching using WiFi.macAddress() when required. Pointers conceptually I get but the allocation and assignment of memory not so.

@armor,
Re understanding pointer and memory, perhaps this will help:

Allocation of writeable memory done like this:

         char acMacAddress[18];

This reserves 18 bytes of memory, (an array of characters) indexed from 0 to 17.

The following initialises the array with ASCII characters (zero terminated ‘C’ string):

        strcpy(acMacAddress, "00:00:00:00:00:00");

This is what the memory looks like after this initialisation:

Index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Value ‘0’ ‘0’ “:” ‘0’ ‘0’ “:” ‘0’ ‘0’ “:” ‘0’ ‘0’ “:” ‘0’ ‘0’ “:” ‘0’ ‘0’ 0 (note the zero terminator)

Here is a pointer that points to an array of characters

       char * szpMacAddress;

Assign the pointer with the address of the array:

       szpMacAddress = acMacAddress;    // you don't need the '&' address of token here

print(szpMacAddress) outputs

       00:00:00:00:00:00

print(acMacAddress) outputs the same

    00:00:00:00:00:00

Set the pointer to the last two characters in the string:

    szpMacAddress = &acMacAddress[16];    //  '&' means "**address of**"
   szpMacAddress = acMacAddress +16;      // equivalent to the above

print(szpMacAddress) outputs

   00

You can change the character in the string:

 *szpMacAddress = '1';    // put ASCII '1' into the location **pointed to** by szpMacAddress

print(szpMacAddress) outputs
10

You can allocate a fixed string to the pointer:

   szpMacAddress = "Constant String";

Here the array of characters “Constant String” is sitting in non-volatile memory,

print(szpMacAddress) outputs

   Constant String

You cannot update this string:

    *szpMacAddress = '1';    // The compiler will complain about this

Cheers!

3 Likes

Probably not, since the compiler doesn't really know whether the string literal will be placed in flash or RAM - that would be the linker, but that one doesn't care either. The action will silently fail.
Unlike AVR based Arduinos on these µCs flash and RAM share the same address space and hence it's not obvious for the compiler that this code would attempt a futile action.

1 Like

@ScruffR, thanks for this.

We can change the "Probably not" to a "Particle compiler does not" - just confirmed with a little test.

const char * szp = "123";   
*szp = '1';                              // Compiler *will* complain 
char * szp = "123";   
*szp = '1';                              // Compiler *does not* complain 

So it is up to the programmer to correctly declare the string as a constant.

2 Likes

@UMD, @ScruffR, Thanks doubly again the example and the clarification makes it very clear.

1 Like

@armor,

All good!

Passing on past lesson taught to me by @ScruffR, if you are happy with the results, you should mark this ticket as solved by pressing the grey little tick box at the bottom of your inputs area so as to mark it as a “solution”.

1 Like