Setup is CW7.2, targetting Coldfire V2 MCF52259 micro.
I've been trying to work this out but my brain is now well and truly melted.
In the project I'm working on, we need to store a lot of (constant) message strings to display to the user, ideally by reference:
messages[MSG_HELLO_WORLD] points to a string "Hello world", but if we later update the messages file so that messages[MSG_HELLO_WORLD] = "Hi, world!" we don't need to change every reference in the code to MSG_HELLO_WORLD to output the updated string.
To complicate things, we have several different languages which we need to reference based on the language the user has set, so ideally we'd be able to do this sort of thing:
printf(messages[lang][MSG_HELLO_WORLD]);
We need to store this lot as constant global data (so it lives in the micro's flash as .rodata), and define it somehow, but at that point there seems to be so many ways to do this (or so many different names for the same thing) that I can't work out how to go about it;
My initial thoughts are to do something like this:
messages.h
extern const char *msg[NUM_LANGUAGES][NUM_MESSAGES];
messages.c
enum { LANGUAGE_ENGLISH, LANGUAGE_FRENCH, NUM_LANGUAGES }; const char *msg[][] = {{[MSG_HELLO_WORLD] = "Hello, world", [MSG_GOODBYE] = "Goodbye"} , {[MSG_HELLO_WORLD] = "Bonjour", [MSG_GOODBYE] = "Au revoir"}}; void say_hello(void) { printf(msg[LANGUAGE_ENGLISH][MSG_HELLO_WORLD]); // Should print "Hello, world" printf(msg[LANGUAGE_FRENCH][MSG_HELLO_WORLD]); // Should print "Bonjour" }
Does this seem like a sensible/correct way to go about things?
Solved! Go to Solution.
const char * const messages[x][y]={};
is the correct type if you do not want the strings to be changeable dynamically.
If you get an error, please copy paste the exact error message here.
Just make sure the declaration and the definition have the same signature.
Without the leading const the compiler would let you write into the string constants without warning.
So that does work, but it allows non working code to be written which gets caught by the initial const, so I would make sure to have both const's.
Daniel
You've got most of it right, save for the multi-dimensional array syntax.
const char *msg[NUM_LANGUAGES][NUM_MESSAGES] =
{
{ /* English */
"Hello, world",
"Goodbye",
},
{ /* French */
"Bonjour",
"Au revoir",
}
};
Also, never use printf for anything but debug purposes.
Thanks Lundin,
Due to the number of elements we're using, and the potential to add/remove/change stuff, I was using selective definition by elements, something I've just sorted out for another similar problem:
https://community.freescale.com/message/70939#70939
Using your example, I would define the messages like this:
const char *msg[NUM_LANGUAGES][NUM_MESSAGES] =
{
{ /* English */
[MSG_HELLO_WORLD] = "Hello, world",
[MSG_GOODBYE] = "Goodbye",
},
{ /* French */
[MSG_HELLO_WORLD] = "Bonjour",
[MSG_GOODBYE] = "Au revoir",
}
};
Is that correct?
We define the first (0) element for each language as [MSG_NOT_DEFINED] = "" or similar, to catch elements which were not properly defined.
I only used printf() as an example, our project doesn't use stdio at all.
No, that code will not compile. What you are using is called "designated initializers", which comes from the C99 standard. C99 received massive criticism when it was released and therefore never became industry standard, even though it is standardized by ISO. No single compiler exists that implements C99 fully (including Codewarrior), and code written in C99 will thus be completely unportable. No static analyzers support C99, nor can you port C99 to C++, nor does MISRA-C support it. And so on.
So forget that you've ever heard about C99 and use the code from my example.
Elements not initialized anywhere will be NULL, no "", and using NULL with a function like printf is undefined, probably crashes but not even that is sure. So the code should probably check shortly at runtime that all the strings are in the table upfront.
If the array is not changed at runtime, make it const so that the array itself ends up in flash.
const char * const msg[NUM_LANGUAGES][NUM_MESSAGES] =...
The leading const states that the strings the array points to are constant (which they are anyhow as those are string literals here).
While I in general not like C macros much, I've used macros in the past to initialize string tables and assign names to them.
E.g. (just pseudo code)
in a header stringtable.h
#define NUM_LANGUAGES 2
#define LANG_ENGLISH_INDEX 0
#define LANG_GERMAN_INDEX 1
#ifdef DEFINE_STRING_TABLE
extern const char* const [][NUM_LANGUAGES] = {
#define TABLE_ENTRY(name, eng, german) {(eng), (german) },
#else
enum stringTableIndex {
#define TABLE_ENTRY(name, eng, german) name,
#endif
TABLE_ENTRY(HELLO_STR, "Hello", "Hallo")
TABLE_ENTRY(WORLD_STR, "world", "Welt")
#ifdef DEFINE_STRING_TABLE
};
#else
};
#endif
And then in one dedicated file (say stringtable.c)
#define DEFINE_STRING_TABLE
#include "stringtable.h"
With such a setup the names and the indexes match automatically and I would hope other programmers can easily add their own strings. One downside is though that the table lists the translations for one string together, whereas the C99 style allows per language blocks.
Daniel
const char * const msg
Oops! Yes, this is very important, or CW will silently allocate the whole thing in RAM. Thanks for correcting me.
Lundin, are you saying the example I gave will not compile, or the example CompilerGuru gave will not compile?
Although using this C99 feature (which is also in GCC) may slightly reduce compatability, we're not porting the code to different platforms or ever likely to move to C++ and it's more helpful to save a large chunk of RAM on the project now.
You should ignore the whole idea of printf, we're using proprietary display hardware & routines.
I forgot to mention that we are enumerating the message names as well as the language names, but hopefully that was implicit.
Compilerguru - I'm not 100% sure I understand your example, it seems like it may get a bit complicated for 100+ strings in 5 or more different languages which is why designated initialisers make life so much easier (and also make the code much more readable)
Your example. GCC uses their own non-standard, which doesn't conform fully to neither the C99 nor the C90 standard. You should write all code in C90, for portability and readability reasons. Many C programmers don't even know about the C99 standard, let alone its syntax.
You will always have to assume that the code will get ported. What happens when (not if) Freescale discontinues the MCU you are writing the program for?
Lundin - if the MCU gets discontinuted and the code gets ported then we have to redesign the product anyway, and re-spec the hardware and re-write code to suit... just as we're doing now moving away from MMC2114 which has its own peculiarities.
Daniel, - I have reinstated the first "const" and it now compiles with no error, not sure what happened there.
Thanks for your help guys, it seems to be working as it should, hideous unportability and unconformity aside :-p
FridgeFreezer,
For me Compilerguru's example seems easier to go than yours. All strungs enums and all localization variants for all MSG_xxxx will be defined in one place, in some strings header. In that header you would have
// enum english german
TABLE_ENTRY(MSG_HELLO, "Hello", "Hallo")
...
TABLE_ENTRY(MSG_WORLD, "world", "Welt")
etc all enums and all strings for all languages
When you include this header in some sourceX.c, in case DEFINE_STRING_TABLE is not defined sourceX will see all MSG_xxx enums. In one dedicated sourceDEF.c file you define DEFINE_STRING_TABLE and include the same strings header. In this case all strings will be defined (and allocated etc) in this sourceDEF.c file.
So in single file you have
#define DEFINE_STRING_TABLE
#include "mystrings.h"
and strings are defined
In all other files you have DEFINE_STRING_TABLE not defined, include "mystrings.h" and reference all required strings via MSG_xxx enums
Compilerguru's example defines two languages. It's easy to add more languages, just edit two instances of TABLE_ENTRY define:
#define TABLE_ENTRY(name, eng, german, japanese) {(eng), (german) ,(japanese) },
and
#define TABLE_ENTRY(name, eng, german, japanese) name,
You can add more languages the same way.
Well I've done some testing, and this seems to work:
char * const messages[5][8] = { { /* English */ [1] = "Hello, world", [5] = "Goodbye" }, { /* French */ [1] = "Bonjour", [5] = "Au revoir" }, { /* Gibberish */ [1] = "Morning", [5] = "Byeee" } };
If I put const char * const messages[x][y] the compiler throws an error about types.
Looking at the .xMAP file the data is now listed as .rodata and shows up at the correct memory location, so all seems well.
const char * const messages[x][y]={};
is the correct type if you do not want the strings to be changeable dynamically.
If you get an error, please copy paste the exact error message here.
Just make sure the declaration and the definition have the same signature.
Without the leading const the compiler would let you write into the string constants without warning.
So that does work, but it allows non working code to be written which gets caught by the initial const, so I would make sure to have both const's.
Daniel