Search code examples
cstringstructmemcpystrcpy

Regarding structs in C, how to copy the data from a struct to a string array


I have a program if finds servers on the network using mDNS. It is from a opensource stack. Currently, I am in need of guidance for the following use case. USECASE: Whenever I run the program to findServers, I plan to add an additional logic, which tries to connect to the servers mentioned, and if the connection has failed, I print a warning message saying the network connection on the server could be faulty.

So to the code,

it has a struct of type as defined below

typedef struct {
    size_t length; /* The length of the string */
    UA_Byte *data; /* The content (not null-terminated) */
} UA_String;

typedef struct {
    UA_UInt32 recordId;
    UA_String serverName;
    UA_String discoveryUrl;
    size_t serverCapabilitiesSize;
    UA_String *serverCapabilities;
} UA_ServerOnNetwork;

The default code has the logic which runs this way:

for(size_t i = 0; i < serverOnNetworkSize; i++) {
            UA_ServerOnNetwork *server = &serverOnNetwork[i];
            printf("Server[%lu]: %.*s", (unsigned long) i,
                   (int) server->serverName.length, server->serverName.data);
            printf("\n\tRecordID: %d", server->recordId);
            printf("\n\tDiscovery URL: %.*s", (int) server->discoveryUrl.length,
                   server->discoveryUrl.data);



            printf("\n\tCapabilities: ");
            /*for(size_t j = 0; j < server->serverCapabilitiesSize; j++) {
                printf("%.*s,", (int) server->serverCapabilities[j].length,
                       server->serverCapabilities[j].data);
            }*/

            //added below
            printf("%.*s", (int) server->serverCapabilities[0].length,
                                   server->serverCapabilities[0].data);
            printf("\n\tStatus: ");
            printf("%.*s", (int) server->serverCapabilities[1].length,
                                               server->serverCapabilities[1].data);


            printf("\n\n");
        }

And the output observed is of the form

Server[0]: name1
    RecordID: 0
    Discovery URL: opc.tcp://hostname2:4840
    Capabilities: LDSME-DESKTOPSIDE
    Status: Available

Server[1]: name2
    RecordID: 1
    Discovery URL: opc.tcp://hostname:4842
    Capabilities: Crane
    Status: Starting...

Server[2]: hostname
    RecordID: 2
    Discovery URL: opc.tcp://hostname:4840
    Capabilities: LDSME-NOTEBOOKSIDE
    Status: AVailable

This would be the default case. But I plan to ping each of the URL's mentioned( or try to send a message) to check if the network is all fine. So I plan to extract the URL information.

Hence I declared a character array A, and tried copied the contents from the server->discoveryURL.data to the array A, using strcpy and memcpy function.

But it fails.

for(size_t i = 0; i < serverOnNetworkSize; i++) {
           UA_ServerOnNetwork *server = &serverOnNetwork[i];
           strcpy(A[i], server->discoveryUrl.data);
           printf("URL %d: %s\n",(unsigned long) i,A[i]);

        }

It fails and does not even run through the loop. Need some guidance to have an output of the below format

URL 0 : opc.tcp://hostname2:4840
URL 1 : opc.tcp://hostname:4842
URL 2 : opc.tcp://hostname:4840

Also I do not understand why, in the printf statement of a struct string "%s" gives an additional character at the end, while "%.*s" gives the correct output. Please looking forward for guidance.

EDIT: I have modified the code a bit and have introduced a new global character Array, and have used memcpy functions. But I am struggling as I am getting an extra character in the URL field.

char *A[] = {};
int main(){

for(size_t i = 0; i < serverOnNetworkSize; i++) {
           UA_ServerOnNetwork *server = &serverOnNetwork[i];        
           A[i] = (char*)UA_malloc(server->discoveryUrl.length+1); 
       memcpy(A[i],server->discoveryUrl.data,server->discoveryUrl.length);  
           printf("URL %d: %.*s\n",(unsigned long) i,A[i]);
        }
}

The output is seen as :

URL 0: opc.tcp://o755-gksr:48401
URL 1: opc.tcp://o755-gksr:48421

There is an extra character 1 at the end which is wrong. Any guidance on how to handle that please.


Solution

  • You need to terminate the string data with a '\0' after copying it. That way printf's %s will know when to stop:

    // first we need some memory for the destination of the copy
    A[i] = malloc(server->discoveryUrl.length+1); // +1 for the '\0' at the end
    // then we copy exactly the number of characters we want
    strncpy(A[i], server->discoveryUrl.data, server->discoveryUrl.length);
    // then we add the '\0' terminator
    A[i][server->discoveryUrl.length] = '\0'; // add the '\0' at the end
    

    Here's a sample:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <inttypes.h>
    
    // defien these so we don't need mDNS
    typedef char UA_Byte;
    typedef uint32_t UA_UInt32;
    
    typedef struct {
        size_t length; /* The length of the string */
        UA_Byte *data; /* The content (not null-terminated) */
    } UA_String;
    
    typedef struct {
        UA_UInt32 recordId;
        UA_String serverName;
        UA_String discoveryUrl;
        size_t serverCapabilitiesSize;
        UA_String *serverCapabilities;
    } UA_ServerOnNetwork;
    
    // for testing we only need one of these
    UA_ServerOnNetwork serverOnNetwork[1] = {
        {
            1,          // recordId
            {3,"abcd"}, // serverName
            {3,"efgh"}, // discoveryUrl
            0,          // serverCapabilitiesSize
            NULL        // serverCapabilities
        }
    };
    int serverOnNetworkSize = sizeof(serverOnNetwork)/sizeof(serverOnNetwork[0]);
    
    int main() {
    
        // first print it using the %.*s printf formatter
        for(size_t i = 0; i < serverOnNetworkSize; i++) {
            UA_ServerOnNetwork *server = &serverOnNetwork[i];
            printf("Server[%lu]: %.*s", (unsigned long) i,
                    (int) server->serverName.length, server->serverName.data);
            printf("\n\tRecordID: %d", server->recordId);
            printf("\n\tDiscovery URL: %.*s", (int) server->discoveryUrl.length,
                    server->discoveryUrl.data);
        }
    
        // now make an array of '\0' terminated strings, fill them in, and print them
        printf("\n\nURLs:");
        char *A[serverOnNetworkSize];
        for(size_t i = 0; i < serverOnNetworkSize; i++) {
            UA_ServerOnNetwork *server = &serverOnNetwork[i];
            // first we need some memory for the destination of the copy
            A[i] = malloc(server->discoveryUrl.length+1); // +1 for the '\0' at the end
            // then we copy exactly the number of characters we want
            strncpy(A[i], server->discoveryUrl.data, server->discoveryUrl.length);
            // then we add the '\0' terminator
            A[i][server->discoveryUrl.length] = '\0'; // add the '\0' at the end
            // and finally we print it
            printf("\n\tURL %lu: %s\n", (unsigned long) i, A[i]);
        }
    
        return 0;
    }
    

    Try it here: https://godbolt.org/z/cbcsdG