I am currently learning C. My ecosystem consists of an Espressif ESP-32 micro controller and Eclipse CDT IDE. 

I am trying to convert an `uint8_t[]` to a human readable `IPv4 address string`. I came up with the following code so far:

```
void app_main() {
    uint8_t[] ip = {192, 168, 0, 99};
    dump_ip(ip);
}

void dump_ip(const uint8_t *in) {
    // ip addresses consist of three dots + terminator '\0'
    int size = 4;
    // count amount of chars needed for specific ip address
    for(int i=0; i<4; i++) {
        if(in[i]==0) {
            size ++;
        } else {
            size += ((int)log10(in[i]))+1;
        }
    }
    // allocate memory on heap for an ip address of length 'size'
    char *ip_str = (char*)malloc(size*sizeof(char));
    // copy ip address parts to char array
    int pos = 0;
    for(int i=0; i<4; i++) {
        if(in[i]==0) {
            ip_str[pos] = '0';
            pos++;
        } else {
            char b[4];
            itoa(in[i], b, 10);
            for(int j=0; j<3; j++) {
                if(b[j]!='0') {
                    ip_str[pos] = b[j];
                    pos++;
                }
            }
        }
        // add dot between ip address parts
        if(i<3) {
            ip_str[pos] = '.';
            pos++;
        }
    }
    // add terminator at end of string
    ip_str[pos] = '\0';
    // print to uart interface
    uart_send(ip_str);
    // release allocated heap memory
    free(ip_str);
}

```

I get the following results when testing with different IP arrays:

```
uint8_t[] ip = {192, 168, 0, 99};   =>   "192.168.0.99"   => OK
uint8_t[] ip = {192, 168, 1, 99};   =>   "192.168.1"      => FAILED
uint8_t[] ip = {192, 168, 10, 99};  =>   "192.168.1"      => FAILED
uint8_t[] ip = {10, 10, 10, 10};    =>   "1"              => FAILED
// etc. etc. etc. 

```

What am I doing wrong? Is there some more elegant way to accomplish this?