A struct that contains an array of char arrays - c++

I'm trying to define an instance of a struct, and am having particular trouble with setting this one variable. It's an array of char arrays.
Here is my struct in my header file...
struct widget_t {
char *name;
uint8_t numberOfNicknames;
char *nicknames[];
};
And here's me attempting to set up the instance of the widget_t struct...
widget_t SomeWidget;
void setUpFunction () {
SomeWidget.name = (char *)"Lawn Mower";
SomeWidget.numberOfNicknames = 2;
SomeWidget.nicknames = {
"Choppie McGrasschopper",
"Really Noisy"
};
}
So, the error is happening when I try to put the nicknames into SomeWidget.nicknames. I'm not sure if I need to do something funky like I'm doing with name being a pointer...?
The tricky bit is that the number of nicknames is variable. So each instance will want to set up a different number of them.

The problem you have, is that c++ does not support variable arrays. Instead you will have to allocate memory dynamically using new or in your case new[].
First you need to change your data type to char**, almost equaliant to the one before. Then you can allocate as many strings you want like this nicknames = new char*[number_of_nicknames].
Important is that with this method you will have to delte your nicknames manually like this: delete[] nicknames;. The best way to accomplish this is using RAII (delete your nicknames in your deconstructor)
When you have dynamic strings then you would use the following structure
struct widget_t {
// optional constructor to allocate nicknames
~widget_t()
{
for (int i = 0; i < numberOfNicknames; ++i)
{
char* nickname = nicknames[i];
if (nickname)
delete[] nickname;
}
delete[] nicknames;
}
char *name;
uint8_t numberOfNicknames;
char **nicknames = NULL;
};
and with constant string the next
struct widget_t {
// optional constructor to allocate nicknames
// allocate nicknames like
// -> nicknames = new const char*[numberOfNicknames];
~widget_t()
{
if (nicknames) delete[] nicknames;
}
char *name;
uint8_t numberOfNicknames;
const char **nicknames = NULL;
};

One option would be:
struct widget_t {
char const *name;
uint8_t numberOfNicknames;
char const * const *nicknames;
};
static char const *mower_nicknames[] = { "Choppie", "Bob" };
widget_t SomeWidget = { "Lawn Mower", 2, mower_nicknames };
static char const *bus_nicknames[] = { "Wheels", "Go", "Round" };
widget_t OtherWidget = { "Bus", 3, bus_nicknames };
// no setup function needed

What you are trying to do is to assign a string literal to a char * pointer -- that is bad (see more at How to get rid of deprecated conversion from string constant to ‘char*’ warnings in GCC?). Here is an possible approach:
#define MAX_NAME_LEN 128
#define MAX_NICK_NAMES 10
struct widget_t {
char name[MAX_NAME_LEN];
uint8_t numberOfNicknames;
char nicknames[MAX_NICK_NAMES][MAX_NAME_LEN];
};
widget_t SomeWidget;
void setUpFunction () {
strcpy(SomeWidget.name, "Lawn Mower");
SomeWidget.numberOfNicknames = 2;
strcpy(SomeWidget.nicknames[0], "Choppie McGrasschopper");
strcpy(SomeWidget.nicknames[1], "Really Noisy");
}
Anyway, since you tag your question with C++ I would suggest you to use std::string and std::vector instead.

There are different problems here.
First your last member in of an incomplete type because it is an array of undeclared dimension. This is not allowed in C++ but most compilers allows it as an extension with same semantics as C. This is rather tricky to use anyway, because it can only be used with allocated structs, where you allocate memory for the struct itself and the incomplete array.
Next, you are trying to assign to a array. You cannot. Arrays are not first class objects in C++ (nor in C). You can initialize an array as a whole, but can only assign to an array element.
And last, you are assigning a C litteral string to a char *. This is bad, because the standard declares that litteral strings are const so using later the pointer to change a char would be Undefined Behaviour. It will work if you do not, but the pointers should at least be declared as const.
Here is how you could use all that:
widget_t* setUpFunction () {
// allocates a widget_t with 2 slots in nicknames
widget_t *someWidget = (widget_t *) malloc(sizeof(widget_t) + 2 * sizeof(char *));
someWidget.name = (char *)"Lawn Mower"; // VERY DANGEROUS: pointer should be const
someWidget.numberOfNicknames = 2;
someWidget.nicknames[0] = (char *) "Choppie McGrasschopper"; // SAME DANGER
someWidget.nicknames[1] = (char *) "Really Noisy" // Still same danger
return widget_t;
}
But all this is rather C-ish and should be avoided in C++. In addition, it still requires allocated memory, which may not be what you want for Arduino

Related

Initializing to a dynamically allocated const char array from a const char array

I am trying to write code that will assign a const char array to a dynamically allocated const char array.
I tried to do it like
const char staticArray[4] = "abc";
const char * ptrDynamicArray;
ptrDynamicArray = new const char[4](staticArray);
But I get an error ("parenthesized initializer in array new [-fpermissive]").
I have two questions:
How could I solve it - like turn it off (I am using Code::Blocks 16.01)?
Can I initialize a dynamically allocated const char array somehow else?
Overloading new operator will do your job.
void * operator new[](size_t n, const char *s) {
void *p = malloc(n);
strcpy((char *)p, s);
return p;
}
Now new operator can be called like :
const char staticArray[4] = "abc";
const char * ptrDynamicArray;
ptrDynamicArray = new (staticArray) char[4];
You cannot copy-initialize an array from another array directly, hence the error. You either need to manually copy the elements (or use std::copy), or, better, if you want a "copy"-able array use std::array<char, 4> instead.
But, as mentioned in the comments, the best probably is to use an std::string here, and use its std::string::c_str() member function in case you need to pass around const char* pointers to old C-like interfaces.

How can I declare a structure of an unknow size?

I have this structure :
struct __attribute__((packed)) BabelPacket
{
unsigned senderId;
unsigned dataLength;
unsigned char data[0];
};
And to declare it I do :
BabelPacket *packet = reinterpret_cast<BabelPacket *>(new char[sizeof(BabelPacket) + 5]);
packet->senderId = 1;
packet->data = "kappa";
packet->dataLength = 5;
But when I compile I have this error :
error: incompatible types in assignment of ‘const char [6]’ to ‘unsigned char [0]’
packet->data = "kappa";
^
Have you an idea how I can do that ?
And I need to send this structure through a socket, to get the object back in my server, so I can use only C types.
If this was a C program, the error you get is because you try to assign to an array, which is not possible. You can only copy to an array:
memcpy(packet->data, "kappa", 5);
Also note that if you want the data to be a C string, you need to allocate an extra character for the string terminator '\0'. Then you can use strcpy instead of memcpy above. Or strncpy to copy at most a specific amount of characters, but then you might need to manually terminate the string.
However, this should not work in C++ at all, unless your compiler have it as an extension.
You can't assign a literal string that way. You'll need to allocate additional memory for the string, then copy to the data pointer.
struct A {
size_t datasize;
char data[0]; // flexible member must appear last.
};
A* create_A(const char* str)
{
size_t datasize = strlen(str) + 1; // null terminated (?)
A* p = reinterpret_cast<A*>(new char[sizeof(A) + datasize]);
memcpy(p->data, str, datasize);
p->datasize = datasize;
return p;
}
A* p = create_A("data string");
This solution is only applicable in environments supporting zero-length or flexible arrays. In fact, a better solution may be to write the sockets code in C and export that interface for use in C++.
If you are willing/allowed to change the unsigned char to a regular char, you can use strcpy:
#include <iostream>
#include <stdio.h>
#include <string.h>
struct __attribute__((packed)) BabelPacket
{
unsigned senderId;
unsigned dataLength;
char data[0]; // I changed this to char in order to use strcpy
};
int main(){
BabelPacket *packet = reinterpret_cast<BabelPacket *>(new char[sizeof(BabelPacket) + 5]);
packet->senderId = 1;
// Copy the string. Add NULL character at the end of
// the string to indicate its end
strcpy(packet->data, "kappa\0");
packet->dataLength = 5;
// Verify that the string is copied properly
for (int i=0;i<packet->dataLength;++i){
std::cout<<packet->data[i];
}
std::cout<<std::endl;
return 0;
}
Note that this will only work if data is at the end of the struct, otherwise there is no contiguous memory to allocate data. If I swap the order of the elements to:
struct __attribute__((packed)) BabelPacket
{
unsigned senderId;
char data[0]; // I changed this to char in order to use strcpy
unsigned dataLength;
};
the output of the code above (instead of "kappa"), would be "a".
A more reliable way if you are determined to use C-arrays would be to assume a maximum number of elements and preallocate the array, i.e.:
#include <iostream>
#include <stdio.h>
#include <string.h>
#define MAX_NUMBER_OF_CHARACTERS 5 // Many ways to do this, I defined the macro for the purposes of this example
struct __attribute__((packed)) BabelPacket
{
unsigned senderId;
// I changed this to char in order to use strcpy. Allocate the
// max number + 1 element for the termination string
char data[MAX_NUMBER_OF_CHARACTERS+1];
unsigned dataLength;
};
int main(){
BabelPacket *packet = reinterpret_cast<BabelPacket *>(new char[sizeof(BabelPacket) + 5]);
packet->senderId = 1;
packet->dataLength = 5;
if (dataLength>MAX_NUMBER_OF_CHARACTERS){
std::cout<<"String greater than the maximum number of characters"<<std::endl;
}
// Copy the string. Add NULL character at the end of
// the string to indicate its end
strcpy(packet->data, "kappa\0");
// Verify that the string is copied properly
for (int i=0;i<packet->dataLength;++i){
std::cout<<packet->data[i];
}
std::cout<<std::endl;
return 0;
}
This code produces the correct output, and protects you against violations. As you can see, it can get messy pretty quickly, which is why I would recommend to use std::vector for this. The dataLength may then be retrieved automatically as the size of the vector, and you are always protected against overflows.

Create a string which will use a previous integer as the size

why it's not possible to create an char array in this way?
int main()
{
int len;
cin>>len;
char input_str[len];
cin>>input_str;
cout<<input_str;
return 0;
}
You cannot use a static array without knowing the size of the array at runtime. Instead you can use a pointer and use either "malloc" or "new" to allocate memory for the array dynamically:
1) First check that the user has entered a valid int
2) Once you have a valid int to work with, you can use...
char *input_str = new char[len];
for C++ or if you have to stick with plain old C use ...
char *input_str = (char *)malloc(len * sizeof(char));

C++ Error: Incompatible types in assignment of ‘char*’ to ‘char [2]

I have a bit of a problem with my constructor.
In my header file I declare:
char short_name_[2];
and other variables
In my constructor:
Territory(std::string name, char short_name[2], Player* owner, char units);
void setShortName(char* short_name);
inline const char (&getShortName() const)[2] { return short_name_; }
In my cpp file:
Territory::Territory(std::string name, char short_name[2], Player* owner,
char units) : name_(name), short_name_(short_name),
owner_(owner), units_(units)
{ }
My error:
Territory.cpp: In constructor ‘Territory::Territory(std::string,
char*, Player*, char)’: Territory.cpp:15:33: error: incompatible types
in assignment of ‘char*’ to ‘char [2]’
I already figured out that char[2] <=> char* but I'm not sure how to handle this about my constructor and get/setters.
Raw arrays in C++ are kind of annoying and fraught with peril. This is why unless you have a very good reason to you should use std::vector or std::array.
First off, as others have said, char[2] is not the same as char*, or at least not usually. char[2] is a size 2 array of char and char* is a pointer to a char. They often get confused because arrays will decay to a pointer to the first element whenever they need to. So this works:
char foo[2];
char* bar = foo;
But the reverse does not:
const char* bar = "hello";
const char foo[6] = bar; // ERROR
Adding to the confusion, when declaring function parameters, char[] is equivalent to char*. So in your constructor the parameter char short_name[2] is really char* short_name.
Another quirk of arrays is that they cannot be copied like other types (this is one explanation for why arrays in function parameters are treated as pointers). So for example I can not do something like this:
char foo[2] = {'a', 'b'};
char bar[2] = foo;
Instead I have to iterate over the elements of foo and copy them into bar, or use some function which does that for me such as std::copy:
char foo[2] = {'a', 'b'};
char bar[2];
// std::begin and std::end are only available in C++11
std::copy(std::begin(foo), std::end(foo), std::begin(bar));
So in your constructor you have to manually copy the elements of short_name into short_name_:
Territory::Territory(std::string name, char* short_name, Player* owner,
char units) : name_(name), owner_(owner), units_(units)
{
// Note that std::begin and std::end can *not* be used on pointers.
std::copy(short_name, short_name + 2, std::begin(short_name));
}
As you can see this is all very annoying, so unless you have a very good reason you just should use std::vector instead of raw arrays (or in this case probably std::string).
When a function wants an array as argument, it gets a pointer to the first element of an array instead. This pointer cannot be used to initialize an array, because it's a pointer, not an array.
You can write functions that accept references to arrays as arguments:
void i_dont_accept_pointers(const char (array&)[2]) {}
The problem here is, that this array reference cannot be used to initialize another array.
class Foo {
char vars[2];
Foo(const char (args&)[2])
: vars(args) // This will not work
{}
};
C++ 11 introduced std::array to eliminiate this and other problems of arrays. In older versions, you will have to iterate through the array elements and copy them individually or use std::copy.
C++ as C holds most of the rules of C.
In case of C, always use char* to pass array because that how C looks at it. Even sizeof (short_name_) will be 8 or 4 when passed to function.
Now, you have a space of 2 bytes in variable short_name_
Constructor allocated memory for two bytes in short_name_ and you need to copy the bytes into that or use a char * pointer and assume it's size if 2.
Chapter 9 from Expert C Programming: Deep C Secrets is good read to understand it.
For simplicity this could be C style code.
#include <stdio.h>
#include <iostream>
using namespace std;
class Territory {
private:
string s;
char c[2];
public:
Territory(std::string a, char b[2] /*visualize as char *b*/) {
s = a;
c[0] = b[0]; //or use strcpy
c[1] = b[1];
}
void PrintMe() {
printf ("As string %s as char %c or %s\n",this->s.c_str(), c[0],c);
}
};
main () {
Territory a("hello", "h");
a.PrintMe();
}

c++: write a char at a given char* causes segfault

I want to copy a char to an address where a given char* points to.
it's in a function which is called by main:
char data = " ";
myfunction(data, somethingelse);
...
inside the function i have something like
void myfunction(char* data, short somethingelse) {
...
char byte = 0;
inputfilestream.read(&byte, 1);
*data = byte; // here i get the segfault
data++;
...
}
the segfault also comes when i to the copy using strncpy:
strncpy(data, byte, 1);
why is there a segfault? data isn't const and the address where i actually write to is exactly the same as the one where i allocated the data-array. i've tested that multiple times.
thanks in advance.
String literals are readonly. If you want a modifyable string, you must use an array, e.g.:
char data[10];
Or:
char *data = new char[10];
To elaborate a bit more: the type of a string literal is actually const char*. Assigning a string literal to a non-const char* is therefore technically invalid, but most compilers allow it anyway for legacy reasons. Many modern compilers will at least issue a warning when you try to do that.
data is assigned a string literal. String literals are ready only, and writing to them will cause segfaults.
Try this:
char data[10]; // or whatever size you want.
instead.
why is there a segfault? data isn't const and the address where i actually write to is exactly the same as the one where i allocated the data-array.
You didn't allocate anything. char *data = " "; shouldn't even compile in C++. You are assigning a constant string to a non-constant.
char byte = 0;
inputfilestream.read(&byte, 1);
*data = byte; // here i get the segfault
data++; // << How many times?
No problem
#include <stdio.h>
int main(int argc, char **argv)
{
char *data = "Yello"; // or char data[] = "Yello";
*data = 'H';
puts(data); // Hello
}