Strings in C

Thread Starter

Ian0

Joined Aug 7, 2020
9,832
I have a routine that prints strings on an LCD display. There are lots of them depending what the product is doing, none more than 20 characters.
First I put all the strings together in an array - that doesn't make for good software legibility, as the string is in a different place to the routine that displays it.
Then I tried:
DisplayBuffer[]="string_to_print";
LCD_Disp(&DisplayBuffer);

but I realise that copies the string to RAM and then prints it from RAM, which isn't very efficient.
What's the best way?
 

WBahn

Joined Mar 31, 2012
30,067
I have a routine that prints strings on an LCD display. There are lots of them depending what the product is doing, none more than 20 characters.
First I put all the strings together in an array - that doesn't make for good software legibility, as the string is in a different place to the routine that displays it.
Then I tried:
DisplayBuffer[]="string_to_print";
LCD_Disp(&DisplayBuffer);

but I realise that copies the string to RAM and then prints it from RAM, which isn't very efficient.
What's the best way?
Where are you defining DisplayBuffer?

If you change this to

char DisplayBuffer[]="string_to_print";

That will most likely (the compiler writer has options here) initialize the char * known as DisplayBuffer to point to the string literal embedded in the code body (this is common on Princeton architecture targets, but may not be possible on a particular Harvard architecture target.

You don't need the & operator in your LCD_Disp() call, although language standard allows it and specifies that it will reference to the same thing.

But I don't know that this is going to solve your problem, since I'm assuming you want a single buffer that you put strings in and then always pass the same buffer to the LCD function.

Code:
#include <stdio.h>

int main(void)
{
    char *s;
    
    s = "First String";
    printf("#1: %s\n", s);

    s = "Second String";
    printf("#2: %s\n", s);

    s = "Third String";
    printf("#3: %s\n", s);
    
    return 0;
}
Produces:

Code:
#1: First String
#2: Second String
#3: Third String
 

nsaspook

Joined Aug 27, 2009
13,305
To the OP.
For good software design you really don't want to litter the code with a large number of constants in the code. It usually worth the effort to have the LCD_Disp(DisplayBuffer) type function print from a general language text pointer or sets of pointers instead with embedding and copying text sections in code.

If you do need arrays of const strings, it's a good idea to create a enum of string name identifiers to access them easily in the code as a string pointer to print/display.
 

Thread Starter

Ian0

Joined Aug 7, 2020
9,832
To the OP.
For good software design you really don't want to litter the code with a large number of constants in the code. It usually worth the effort to have the LCD_Disp(DisplayBuffer) type function print from a general language text pointer or sets of pointers instead with embedding and copying text sections in code.

If you do need arrays of const strings, it's a good idea to create a enum of string name identifiers to access them easily in the code as a string pointer to print/display.
Now that's a good idea. I tried putting them all in one file, and numbering them, and kept getting the wrong number. I was reasonably sure I wasn't the first person ever to have this problem, therefore someone must have a good solution to it.
Two things in favour of littering the text with string constants:
1) You never print the wrong string
2) If the string is used only once, it is memory-efficient. (If printed more than once, it isn't)
 
Last edited:

nsaspook

Joined Aug 27, 2009
13,305
Now that's a good idea. I tried putting them all in one file, and numbering them, and kept getting the wrong number. I was reasonably sure I wasn't the first person ever to have this problem, therefore someone must have a good solution to it.
It's easy to auto size the array with the enum and let the compiler check common programming errors.

Here I have a name array for JSON data name identifiers that matches an array to put that data.
C:
const char* mqtt_name[V_DLAST] = {
    "pccmode",
    "batenergykw",
    "runtime",
    "bvolts",
    "load",
    "solar",
    "acenergy",
    "flast",
    "DLv_pv",
    "DLp_pv",
    "DLp_bat",
    "DLv_bat",
    "DLc_mppt",
    "DLgti",
};
I create enums to create and index.

C:
    enum iammeter_id {
        IA_VOLTAGE,
        IA_CURRENT,
        IA_POWER,
        IA_IMPORT,
        IA_EXPORT,
        IA_FREQ,
        IA_PF,
        IA_LAST,
    };

    enum mqtt_vars {
        V_FCCM,
        V_FBEKW,
        V_FRUNT,
        V_FBV,
        V_FLO,
        V_FSO,
        V_FACE,
        V_FLAST,
        // add other data ranges here
        V_DVPV,
        V_DPPV,
        V_DPBAT,
        V_DVBAT,
        V_DCMPPT,
        V_DGTI,
        V_DLAST,
    };

#define MAX_IM_VAR  IA_LAST*PHASE_LAST

    struct energy_type {
        volatile double print_vars[MAX_IM_VAR];
        volatile double im_vars[IA_LAST][PHASE_LAST];
        volatile double mvar[V_DLAST];
        volatile bool once_gti, once_ac, iammeter, fm80, dumpload;
        volatile double gti_low_adj, ac_low_adj;
        volatile bool ac_sw_on, gti_sw_on;
        volatile uint32_t speed_go, rc, im_delay, im_display;
        pthread_mutex_t ha_lock;
    };

    enum mqtt_id {
        P8055_ID,
        FM80_ID,
        DUMPLOAD_ID,
        LAST_MQTT_ID,
    };

    struct ha_flag_type {
        volatile MQTTClient_deliveryToken deliveredtoken, receivedtoken;
        volatile bool runner, rec_ok;
        int32_t ha_id;
        volatile int32_t var_update, energy_mode;
    };
All of that is used in sets of functions that use OOP like functionality to make then handle dynamically changing data on a network processing thread that gets called from a few MQTT topic sources using the same function

C:
/*
* data received on topic from the MQTT broker
*/
int32_t msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message) {
    int32_t i, ret = 1;
    char* payloadptr;
    char buffer[1024];
    struct ha_flag_type *ha_flag = context;

    // bug-out if no context variables passed to callback
    if (context == NULL) {
        ret = -1;
        ha_flag->rec_ok = false;
        goto null_exit;
    }

#ifdef DEBUG_REC
    fprintf(fout, "Message arrived\n");
#endif
    payloadptr = message->payload;
    for (i = 0; i < message->payloadlen; i++) {
        buffer[i] = *payloadptr++;
    }
    buffer[i] = 0; // make C string

    // parse the JSON data
    cJSON *json = cJSON_ParseWithLength(buffer, message->payloadlen);
    if (json == NULL) {
        const char *error_ptr = cJSON_GetErrorPtr();
        if (error_ptr != NULL) {
            fprintf(fout, "Error: %s\n", error_ptr);
        }
        ret = -1;
        ha_flag->rec_ok = false;
        E.fm80 = false;
        E.dumpload = false;
        goto error_exit;
    }

    if (ha_flag->ha_id == FM80_ID) {
#ifdef DEBUG_REC
        fprintf(fout, "FM80 MQTT data\r\n");
#endif
        cJSON *data_result = json;

        for (uint32_t i = V_FCCM; i < V_FLAST; i++) {
            if (json_get_data(json, mqtt_name[i], data_result, i)) {
                ha_flag->var_update++;
            }
        }
        E.fm80 = true;
    }

    if (ha_flag->ha_id == DUMPLOAD_ID) {
#ifdef DEBUG_REC
        fprintf(fout, "DUMPLOAD MQTT data\r\n");
#endif
        cJSON *data_result = json;

        for (uint32_t i = V_DVPV; i < V_DLAST; i++) {
            if (json_get_data(json, mqtt_name[i], data_result, i)) {
                ha_flag->var_update++;
            }
        }
        E.dumpload = true;
    }

    ha_flag->receivedtoken = true;
    ha_flag->rec_ok = true;
error_exit:
    // delete the JSON object
    cJSON_Delete(json);
null_exit:
    MQTTClient_freeMessage(&message);
    MQTTClient_free(topicName);
    return ret;
}
C:
/*
* lock and load mqtt thread received data into buffered program data array
*/
bool json_get_data(cJSON *json_src, const char * data_id, cJSON *name, uint32_t i) {
    bool ret = false;
    static uint32_t j = 0;

    // access the JSON data
    name = cJSON_GetObjectItemCaseSensitive(json_src, data_id);
    if (cJSON_IsString(name) && (name->valuestring != NULL)) {
#ifdef GET_DEBUG
        fprintf(fout, "%s Name: %s\n", data_id, name->valuestring);
#endif
        ret = true;
    }
    if (cJSON_IsNumber(name)) {
#ifdef GET_DEBUG
        fprintf(fout, "%s Value: %f\n", data_id, name->valuedouble);
#endif
        if (i > V_DLAST) {
            i = V_DLAST;
        }
        pthread_mutex_lock(&E.ha_lock);
        E.mvar[i] = name->valuedouble;
        pthread_mutex_unlock(&E.ha_lock);

        if (i == V_DCMPPT) {
            /*
             * load battery current standard deviation array bat_c_std_dev with data
             */
            bsoc_set_std_dev(E.mvar[i], j++);
            if (j >= RDEV_SIZE) {
                j = 0;
            }
        }
        ret = true;
    }
    return ret;
}
C:
#define ADDRESS         "tcp://10.1.1.172:1883"
#define CLIENTID1       "Energy_Mqtt_HA1"
#define CLIENTID2       "Energy_Mqtt_HA2"
#define TOPIC_P         "mateq84/data/gticmd"
#define TOPIC_PACA      "home-assistant/gtiac/availability"
#define TOPIC_PDCA      "home-assistant/gtidc/availability"
#define TOPIC_PACC      "home-assistant/gtiac/contact"
#define TOPIC_PDCC      "home-assistant/gtidc/contact"
#define TOPIC_SS        "mateq84/data/solar"
#define TOPIC_SD        "mateq84/data/dumpload"
#define QOS             1

    MQTTClient_create(&client_p, ADDRESS, CLIENTID1,
            MQTTCLIENT_PERSISTENCE_NONE, NULL);
    conn_opts_p.keepAliveInterval = 20;
    conn_opts_p.cleansession = 1;

    MQTTClient_setCallbacks(client_p, &ha_flag_vars_ss, connlost, msgarrvd, delivered);
    if ((E.rc = MQTTClient_connect(client_p, &conn_opts_p)) != MQTTCLIENT_SUCCESS) {
        printf("Failed to connect, return code %d\n", E.rc);
        pthread_mutex_destroy(&E.ha_lock);
        exit(EXIT_FAILURE);
    }

    MQTTClient_create(&client_sd, ADDRESS, CLIENTID2,
            MQTTCLIENT_PERSISTENCE_NONE, NULL);
    conn_opts_sd.keepAliveInterval = 20;
    conn_opts_sd.cleansession = 1;

    MQTTClient_setCallbacks(client_sd, &ha_flag_vars_sd, connlost, msgarrvd, delivered);
    if ((E.rc = MQTTClient_connect(client_sd, &conn_opts_sd)) != MQTTCLIENT_SUCCESS) {
        printf("Failed to connect, return code %d\n", E.rc);
        pthread_mutex_destroy(&E.ha_lock);
        exit(EXIT_FAILURE);
    }
 
Last edited:

MrAl

Joined Jun 17, 2014
11,489
That is not valid C code. You cannot assign to an array.
Hi,

I rarely ever have to do that because most of the stuff I have programmed do not use constant strings they are all completely variable. One example is storing the location of other data under directories where the directory names and file names have to be stored in character arrays. For example:
"C:\MyDownloads\MyFilename.txt"

but it would be almost never done that way because it would get that file path from somewhere else (such as on the hard drive itself) and so it would never be typed in as constant string. It would be more like:
FilePathIn=GetNextFilePath();

or something like that.

I think you can assign a constant string that way though, with quotes, but it would be rare for me to do that. I can't remember doing that now ever.
 

ApacheKid

Joined Jan 12, 2015
1,617
I have a routine that prints strings on an LCD display. There are lots of them depending what the product is doing, none more than 20 characters.
First I put all the strings together in an array - that doesn't make for good software legibility, as the string is in a different place to the routine that displays it.
Then I tried:
DisplayBuffer[]="string_to_print";
LCD_Disp(&DisplayBuffer);

but I realise that copies the string to RAM and then prints it from RAM, which isn't very efficient.
What's the best way?
As others mention, the compiler writer itself has a lot of options for how it stores strings. In my experience, string constants embedded in code are fine, but however it's done, the compiler typically puts string constants into a "string pool" they are all collected together and put into a dedicated object file section. So go for the most readable from your own perspective, don't worry about "efficiency". Multiple uses of the same literals is fine too, the compiler removes duplicates when it builds the string pool.
 

nsaspook

Joined Aug 27, 2009
13,305
As others mention, the compiler writer itself has a lot of options for how it stores strings. In my experience, string constants embedded in code are fine, but however it's done, the compiler typically puts string constants into a "string pool" they are all collected together and put into a dedicated object file section. So go for the most readable from your own perspective, don't worry about "efficiency". Multiple uses of the same literals is fine too, the compiler removes duplicates when it builds the string pool.
I usually embed error or exception strings in the code sequence that checks or executes for source code clarity but try to group and organize general operations strings out of direct code scope. Your comments about efficiency are right on the money but you need to be more careful with low resource embedded chips and compilers as optimization is often reduced or even turned off for hardware debugging.
 

WBahn

Joined Mar 31, 2012
30,067
As others mention, the compiler writer itself has a lot of options for how it stores strings. In my experience, string constants embedded in code are fine, but however it's done, the compiler typically puts string constants into a "string pool" they are all collected together and put into a dedicated object file section. So go for the most readable from your own perspective, don't worry about "efficiency". Multiple uses of the same literals is fine too, the compiler removes duplicates when it builds the string pool.
Compilers often play even more tricks that can get people in trouble.

One thing that they can look for are strings that have common suffixes. For instance, consider the following:


printf("Name: ");
...
printf("User Name: ");

The compiler may only store the second one and then simply point the first one to the 'N' within the second.

Related cautions are that it is never safe to modify string literals, even if the processor allows it. Extending the above example, we might have

char *name_prompt = "Name: ";
char *user_prompt = "User Name: ";

If we then do

user_prompt[5] = 'n';
printf("%s", name_prompt);

we may very will get 'name: ' instead of 'Name: '.

Worse, we have just entered the world of self-modifying code, and all kinds of demons lurk in that realm.

The compiler may or may not yell at us if we do this, and the hardware may not allow it -- if the code is marked as read-only, then this might cause an access violation and crash the program.

As for worrying about efficiency, in general that's the mindset that needs to be used -- if the code is efficient enough, then it's efficient enough and readability/maintainability trump performance. But since the TS is talking about LCD displays, this is almost certainly some kind of embedded application and efficiency considerations can seldom be cavalierly dismissed when working with resource-starved processors. Most of my embedded programming back in the day was done on PICs using a 32 kHz watch crystal as the oscillator, so I could execute 8192 instructions/second, plus I also was limited to 2K instructions and had all of 128 bytes of RAM to work with -- you can bet I had efficiency very high on my list of priorities.
 

ApacheKid

Joined Jan 12, 2015
1,617
I've done a great deal of "efficiency" work over the years but not with MCUs. The past few days I've been working on an important service that runs on Windows and periodically pulls down data from a remote source, many many gigabytes of data. I have reduced the overall elapsed time and peak memory use by doing so in increments and then retesting under identical conditions.

Like many things "efficiency" is a trade off, memory/CPU/elapsed time, one can often reduce one but at a cost of increasing the others. I personally favor a development approach that emphasizes clarity, readability and then approach performance as a distinct step that I perform later on, on code that I know is working.

Of course there are obvious inefficiencies sometimes and these should be avoided but other than those I wait until after all is testing well before addressing performance.
 

nsaspook

Joined Aug 27, 2009
13,305
Often with small embedded projects, a lack of "efficiency" means it doesn't run at all. You can't compile the code to fit the limited code and memory space.
 

ApacheKid

Joined Jan 12, 2015
1,617
Often with small embedded projects, a lack of "efficiency" means it doesn't run at all. You can't compile the code to fit the limited code and memory space.
Yes, I imagine things can get very "interesting" trying to shave off instructions here and there!
 

BobaMosfet

Joined Jul 1, 2009
2,113
I have a routine that prints strings on an LCD display. There are lots of them depending what the product is doing, none more than 20 characters.
First I put all the strings together in an array - that doesn't make for good software legibility, as the string is in a different place to the routine that displays it.
Then I tried:
DisplayBuffer[]="string_to_print";
LCD_Disp(&DisplayBuffer);

but I realise that copies the string to RAM and then prints it from RAM, which isn't very efficient.
What's the best way?
Where do you think strings you've created at compile time exist? In the symbol table. During execution (aka 'runtime') the must exist in RAM. Your display routine should accept a pointer to the string as an argument, not a string. Just FYI.
 

BobaMosfet

Joined Jul 1, 2009
2,113
Compilers often play even more tricks that can get people in trouble.

One thing that they can look for are strings that have common suffixes. For instance, consider the following:


printf("Name: ");
...
printf("User Name: ");

The compiler may only store the second one and then simply point the first one to the 'N' within the second.

Related cautions are that it is never safe to modify string literals, even if the processor allows it. Extending the above example, we might have

char *name_prompt = "Name: ";
char *user_prompt = "User Name: ";

If we then do

user_prompt[5] = 'n';
printf("%s", name_prompt);

we may very will get 'name: ' instead of 'Name: '.

Worse, we have just entered the world of self-modifying code, and all kinds of demons lurk in that realm.

The compiler may or may not yell at us if we do this, and the hardware may not allow it -- if the code is marked as read-only, then this might cause an access violation and crash the program.

As for worrying about efficiency, in general that's the mindset that needs to be used -- if the code is efficient enough, then it's efficient enough and readability/maintainability trump performance. But since the TS is talking about LCD displays, this is almost certainly some kind of embedded application and efficiency considerations can seldom be cavalierly dismissed when working with resource-starved processors. Most of my embedded programming back in the day was done on PICs using a 32 kHz watch crystal as the oscillator, so I could execute 8192 instructions/second, plus I also was limited to 2K instructions and had all of 128 bytes of RAM to work with -- you can bet I had efficiency very high on my list of priorities.
Ahhhh.... someone else who remembers self modifying code.

And just because I'm on the SoapBox, not directed at WBahn or anyone in particular): Obsolesced by instruction caching. You can still do it, with a nearly unnoticable hit to performance if you're careful and judicious. Embedded systems are still a good place to use self-modifying code if you have the rare need of it. It is neither a good idea or a bad idea to use it- it is a tool in the toolbox for which there are no alternatives in some cases.
 

nsaspook

Joined Aug 27, 2009
13,305
Ahhhh.... someone else who remembers self modifying code.

And just because I'm on the SoapBox, not directed at WBahn or anyone in particular): Obsolesced by instruction caching. You can still do it, with a nearly unnoticable hit to performance if you're careful and judicious. Embedded systems are still a good place to use self-modifying code if you have the rare need of it. It is neither a good idea or a bad idea to use it- it is a tool in the toolbox for which there are no alternatives in some cases.
I've seen a lot of it, in the old days copy protection schemes and still do occasionally, in virus source code as a signature masking technique.
https://en.wikipedia.org/wiki/Metamorphic_code
https://en.wikipedia.org/wiki/Polymorphic_code
 
Top