Common constants for an AVR / Linux GCC C++ project - c++

I'm creating software for an Linux + AVR Arduino project. Obviously the whole work is split in several projects in Eclipse (I'm not using Arduino IDE). I'd like to use common, mostly string, constants for all those projects. I also have to spare microcontroller's RAM so compile-time constants needed. How do I best implement it? My idea is to create a separate, header-only, project for those constants.
Using:
class A {
public:
static const char * const STRING;
static const unsigned char BOOL;
};
is not good enough, because I'd like to be able to concatenate string constants like this:
class A {
public:
static const char * const STRING_PART1;
static const char * const STRING_PART2;
static const unsigned char BOOL;
};
const char * const A::STRING_PART1 = "PART1_";
//const char * const A::STRING_PART2 = A::STRING_PART1 + "PART2"; //obviously won't compile
//const char * const A::STRING_PART2 = strcat("PART2", A::STRING_PART1); //this is not compile-time
I also don't want to use define. I'd like to use:
class A {
public:
static const std::string STRING_PART1;
static const std::string STRING_PART2;
}
which allows for string concatenation and is (AFAIK) compile-time, but std::string is not available in avr projects - or I'm wrong here and just don't know how to use it.
Any help appreciated.

You can continue with the current idea of using const char* const (if std::string is not usable). I would suggest to use #define for assignment purpose only. Example:
class A {
public:
static const char * const STRING_PART1;
static const char * const STRING_PART2;
static const unsigned char BOOL;
};
#define PART1_ "PART1_" // <--- for value assignent
#define PART2_ "PART2_"
const char * const A::STRING_PART1 = PART1_;
const char * const A::STRING_PART2 = PART1_ PART2_; // <--- ok! concatenation by compiler

Compile time for me means it is stored in ROM (eg. microcontroller's flash), where as runtime variables are stored in RAM. In my case, it's RAM I have to spare. The compiler decides where to put variables basing on many rules. Defines are one example of compile-time constants, they clearly don't count up to RAM usage. An other example should be static class constants - but in this case my compiler decides otherwise. Of course, I may be confusing things.
I think that you're in fact confusing things:
defines are not stored in ROM - they are not part of your program at all as they get evaluated by the preprocessor already.
the difference between "compile time" and "run time" you're making applies to evaluation, not storage.
If you want to use program memory (= AVR flash) for constant strings, use the PSTR macro from avr/pgmspace.h.

Related

Why do some libs use non-const char * as function argument?

Sometimes using pure-C libs in my C++ projects, I see strange (in my opinion) function declarations.
E.g.: libldap's ldap_search_ext(): https://linux.die.net/man/3/ldap_search_ext_s
int ldap_search_ext(
LDAP *ld,
char *base,
int scope,
char *filter,
char *attrs[], // this one!
int attrsonly,
LDAPControl **serverctrls,
LDAPControl **clientctrls,
struct timeval *timeout,
int sizelimit,
int *msgidp );
Why can't attrs[] be a const char *?
Declarations like this don't want to change the content of the pointer and generate a lot of issues:
// pure C
void func(char * data[])
{
...
}
func({"blabla"}); // won't work (corrected: yes, this is wrong syntax, but it's true for structs of pointers)
const char *d[] = {"blabla", "blablu"};
func(d); // won't work
// C++
const std::string str("blabla");
char * data[] = { str.data() }; // even non-const won't work (because data() returns const*)
/// etc...
Is there any reason for not declaring such arguments as const?
This is mostly just (due to) a historical wart in the C standard that has never been fixed.
When const was added to C, the ability to implictly (and safely) convert simple pointers was added -- you can implicitly convert a T * to a const T * just fine. However, (safe) conversion of more complex pointer types was missed -- you can't convert a T * const * to a const T * const *. As a result, when a library takes a double pointer like this, it does not have any 'good' way of making it const if it is read only. Making it either a const char ** or a const char * const * would break some uses (requiring messy explicit casts).
Note that allowing implicit conversions of T ** to const T ** would be unsafe -- such a pointer could be used to modify a T * to point at a const T * without a cast.
One likely reason is that string constants in C (unlike C++) are not const.
As such, there was a historical lack of const-correctness in many libraries that should have been there. Had string constants been const, programmers would have been forced to account for it.
When dealing with libraries such as this that you know don't modify the argument, you have to apply a const_cast to make it fit.
char *d[] = {const_cast<char *>("blabla"), const_cast<char *>("blablu")};
func(d);
const std::string str("blabla");
char * data[] = { const_cast<char *>(str.data()) };
func(data);

constexpr const char* in header file

Is there a reason to not use "constexpr const char*" in header file?
The argument from a colleague is that every translation unit including this header file would have a copy.
My understanding was that since its compile-time constant, no memory is allocated and acts more like a "#define" macro with respect to memory usage.
Here is the source,
TestConstExpr.h
#include <string.h>
namespace TestConstExpr
{
constexpr const char* TIME_FORMAT = "yyyy-MM-dd hh:mm:ss";
constexpr const int TIME_FORMAT_SIZE = strlen(TIME_FORMAT) + 1;
class TestClass
{
char arr[TIME_FORMAT_SIZE];
}
}
The argument from a colleague is that every translation unit including this header file would have a copy.
You're colleague is technically correct. But it doesn't matter, since the redundant copies are thrown away when the units are linked together.
Although, I've seen comments about this not being necessarily the case on some non-standard-conforming systems when dynamic linking is involved.
constexpr const char* TIME_FORMAT = "yyyy-MM-dd hh:mm:ss";
.... sizeof(TIME_FORMAT)
This doesn't do what you probably think it does. It gives you the size of the pointer, not the size of the pointed string.
constexpr const int TIME_FORMAT_SIZE = strlen(TIME_FORMAT) + 1;
Your attempted fix also doesn't work, because strlen is not a constant expression.
You can fix the problems by using a reference to the string literal:
static constexpr auto& TIME_FORMAT = "yyyy-MM-dd hh:mm:ss";
constexpr const int TIME_FORMAT_SIZE = sizeof(TIME_FORMAT);

Global Externs. Difference between string and char*

I have two questions:
I used to have a Constants.h containing
const std::string PATH("/ram/")
and it worked fine.
But when I added
const char* BLAH = "blah";
to the same file. I got a redefinition error during linking.
I get that it's because each compilation unit creates its own constants so this causes problem during linking, but why did it work for regular std::strings before and not for char*s?
So after that first issue, I decided to make everything extern and I now have a Constants.h and a Constants.cpp. But then I ran into another problem. I have another file Test.cpp, where a constant is defined as
const std::string FOO(PATH + "booyah");
This used to create "/ram/booyah" but now it's just "booyah". PATH has somehow become empty. I'm guessing it has something to do with the extern, and with constants being created during compile time. The constant PATH works okay during runtime.
Is this correct understanding? Is there a way to make all these work together nicely?
Thanks in advance!
When something is a const it is automatically made static as in internal linkage.
Therefore when you had const std::string it was actually static const std::string.
This means every compilation unit gets its own copy of the string, which is wasteful of memory, although usually not a big concern.
To fix that, make extern const std::string s; in the header file and extern const std::string s = ".."; in the source file.
To avoid requiring a source file, you can try this alternative, although it is potentially longer to compile. It will also allocate the string only once.
inline const std::string& GetStr()
{
static const std::string s = "..";
return s;
}
Now, why didn't const char* BLAH compile in the header? This is because BLAH is NOT a constant. Although it is a read-only pointer, the pointer is not constant, and can be changed to point to something else.
To make the code compile like it did with const std::string, you must use const char *const BLAH = "..";. Now it is a real constant and will compile as a static. This also suffers from the same problem that every compilation unit receives a copy of the pointer. It may also receive a copy of the string literal, depending on compiler optimizations. You can fix this by using the extern method above.
The order in which constants are initialized in the same compilation unit is the order in which they appear. However, the order in which constants are initialized between compilation units is NOT defined. This means an extern from one source file shouldn't depend on an extern from another source file.
You can fix this by defining all your constants in the same source file in the correct order, or using functions (possibly inline) to return the strings.
A global variable that is const has internal linkage. Your BLAH variable is not const, and so it defaults to external linkage. You could either declare it static, or simply make it const:
const char * const BLAH = "blah";
If you find it difficult to spell type names in C++, you can use type aliases to make your life easier:
using ccp = const char *;
const std::string foo = "foo";
const ccp bar = "bar";
Since the header file can be included ffrom multiple CPP files, you would get conflicts. You can use the static keyword.
I typically do:
static const char* const str= "blah";

Global typecast operator overload?

I'm writing some 'portable' code (meaning that it targets 32- and 64-bit MSVC2k10 and GCC on Linux) in which I have, more or less:
typedef unsigned char uint8;
C-strings are always uint8; this is for string-processing reasons. Legacy code needs char compiled as signed, so I can't set compiler switches to default it to unsigned. But if I'm processing a string I can't very well index an array:
char foo[500];
char *ptr = (foo + 4);
*ptr = some_array_that_normalizes_it[*ptr];
You can't index an array with a negative number at run-time without serious consequences. Keeping C-strings unsigned allows for such easier protection from bugs.
I would really like to not have to keep casting (char *) every time I use a function that takes char *'s, and also stop duplicating class functions so that they take either. This is especially a pain because a string constant is implicitly passed as a char *
int foo = strlen("Hello"); // "Hello" is passed as a char *
I want all of these to work:
char foo[500] = "Hello!"; // Works
uint8 foo2[500] = "Hello!"; // Works
uint32 len = strlen(foo); // Works
uint32 len2 = strlen(foo2); // Doesn't work
uint32 len3 = strlen((char *)foo2); // Works
There are probably caveats to allowing implicit type conversions of this nature, however, it'd be nice to use functions that take a char * without a cast every time.
So, I figured something like this would work:
operator char* (const uint8* foo) { return (char *)foo; }
However it does not. I can't figure out any way to make it work. I also can't find anything to tell me why there seems to be no way to do this. I can see the possible logic - implicit conversions like that could be a cause of FAR too many bugs - but I can't find anything that says "this will not work in C++" or why, or how to make it work (short of making uin8 a class which is ridiculous).
Global cast(typecast) operator, global assignment operator, global array subscript operator and global function call operator overloading are not allowed in C++.
MSVS C++ will be generate C2801 errors on them. Look at wiki for list of C++ operators and them overloading rules.
I'm not a big fan of operator [ab]using, but thats what c++ is for right?
You can do the following:
const char* operator+(const uint8* foo)
{
return (const char *)foo;
}
char* operator+(uint8* foo)
{
return (char *)foo;
}
With those defined, your example from above:
uint32 len2 = strlen(foo2);
will become
uint32 len2 = strlen(+foo2);
It is not an automatic cast, but this way you have an easy, yet explicit way of doing it.
Both compilers you mention do have a "treat chars as unsigned" switch. Why not use that?

c++ constant in library; does not work

anyone knows why this does not work when I try to include a library with the following declarations:
namespace wincabase
{
const char* SOMESTRING = "xx";
}
While this is perfectly fine:
namespace wincabase
{
const int X = 30;
}
I get a "multiple definitions" error with gcc for the first case when I link the lib. Thanks!
const char* means pointer to const char. This means the pointer itself is not constant.
Hence it's a normal variable, so you'd need to use
extern const char* SOMESTRING;
in the header file, and
const char* SOMESTRING = "xx";
in one compilation unit of the library.
Alternatively, if it's meant to be a const pointer to a const char, then you should use:
const char* const SOMESTRING = "xx";
You're declaring the pointer as const, and then pointing it to a string literal defined in the compilation unit, so you'd be duplicating the string literal if you used this in a header file. What you need to do is declare pointer in the header file, and define the string in a source file in the library.
Header:
extern const char* SOMESTRING;
In some source file in the library:
const char* SOMESTRING = "xx";
Besides the approach Tobi pointed out:
const char* const SOMESTRING = "xx";
another alternative is to declare it as a const character array:
const char SOMESTRING[] = "xx";
This approach potentially provides the compiler with additional optimization opportunities, such as placing the string in the read-only section of the resulting binary; although it's conceivable the compiler may be able to perform similar optimizations with the first approach.
You need to declare and define them seprately:
Plop.h
======
namespace wincabase
{
extern const char* SOMESTRING; // declare
}
Plop.cpp
========
const char* wincabase::SOMESTRING = "xx"; // define