1

I want to create a dynamic sized array of chars as a member in a class. This is being done inside a library that I've created. I have the .h and the .cpp files created. I'm not sure how to declare the array in the .h file. I've tried a few things so far: char* content; char content[]; But these don't seem to work.

Here's what I have in my .h file:

class bufr{
    public:
    //constructor and deconstructor
    bufr(char* _chars, uint8_t _len);
    ~bufr();

    //variables
    uint8_t len;
    char content[];

};

Here's what I have in my .cpp file (this is the constructor):

bufr::bufr(char* _chars, uint8_t _len){
    char* content = new char[_len];
    content = _chars;
    len = _len;
}

I've also tried replacing the line content = _chars with:

for (int i = 0; i < _len; i++){
    content[i] = _chars[i];
}

But this also doesn't work. When I print the content member to the serial port from within the constructor, or in the main loop it prints nonsense characters, not what I passed it.

Here's my main loop:

void loop(){
    char chars[] = "1234";
    bufr buf(chars, 4);
}

Have I explained myself well enough? Can anyone give me a hand? Much appreciated!

Oh, and yes, I know - in my deconstructor that's not shown, I have delete [] content

I plan on using this class nested inside another class in a few spots and hoping to take advantage of the dynamic mem allocation. I know I can simply declare an array of fixed length, but I wanted to learn the proper way to use new and delete.

Thanks!

1
  • Actually, I lied above... I was just doing some more testing. If I print to the serial port from WITHIN the constructor the member content is printed correctly: "1234". But, if I print from the main loop like this: serial.println(buf.content); shows up incorrectly. What am I doing wrong?
    – Killerb81
    Commented Jun 15, 2016 at 3:14

2 Answers 2

1

This is wrong on a number of levels. First in the class declaration:

char content[];

That gives you a pointer to an array of zero length.

This gives you an array of char, of zero length. This is not useful.

char* content = new char[_len];
content = _chars;

That does a new into a new variable content - not the class variable. Then you assign (overwrite) the passed pointer into content (discarding the memory you just allocated). That pointer is then discarded when the constructor ends. Perhaps using strcpy would be better.

I made up a small example based on your code which prints the correct result:

class bufr 
  {
  public:
    //constructor and deconstructor
    bufr(char* chars, uint8_t len);
    ~bufr();

    //variables
    uint8_t len_;
    char * content_;
  };    // end of class bufr

bufr::bufr(char* chars, uint8_t len) 
  {
  content_ = new char[len];
  memcpy (content_, chars, len);  // copy into allocated memory
  len_ = len;   // remember length
  }   // end of constructor

bufr::~bufr()
{
  delete [] content_;
} // end of destructor

void setup() 
{
  Serial.begin (115200);

  char chars[] = "1234 ";
  bufr buf(chars, sizeof chars);

  Serial.println (buf.content_);
} // end of setup

void loop() 
{
} // end of loop

Can you explain to me why it was making a new variable content and not using the class variable?

Small example:

char * content;

void loop ()
  {
  char * content = "foo";  // makes a NEW variable
  }

In this example there are two variables named content. The one in the function "shadows" the global one. However the code below only has one variable:

char * content;

void loop ()
  {
  content = "foo";  // uses existing variable
  }

In the destructor, when you delete the pointer to the array, does this free up the memory allocated to the array? It's now free to be assigned again by something else? Or does it sit there still initialized as that array, not being able to be used again?

You aren't deleting the pointer. You are deleting the memory it points to. The pointer is now pointing to freed memory, so using it as a pointer at this stage would be wrong. You can do another new to make it point to something else.


they delete pointer and then delete[] pointer.

You misinterpreted that. You either delete pointer or delete [] pointer depending on whether pointer is an array or not. I reworked content to not be an array, thus deleting it without the brackets is correct however as it is allocated using new [] it must be deleted with delete [].


on the link posted above they say to use delete this way: delete pointer if it was a single element allocated with new or delete [] pointer to release memory allocation for arrays of elements allocated with new and a size in []'s. So in my case I want to use delete [] content_?

No, you don't want to be setting up an array in the first place. Take a look at this small example:

struct   {
  char bar [];
  } foo;

void setup() 
{
Serial.begin (115200);
Serial.println ("Starting");
Serial.println (sizeof (foo));
Serial.println (sizeof (foo.bar));
}

void loop() 
{
}

That prints:

Starting
0
0

The variable bar is a zero-length array. Thus the entire struct foo is also zero length. It isn't even a pointer (that would be two bytes on this platform).

It is wrong to use an array of char like that, when you want to dynamically allocate memory. Look at this:

char foo [4];

That allocates 4 bytes of memory for foo. You can't later on make it longer by doing a new.

What you want is:

char * foo;

Now you have allocated a pointer and can do a new, like this:

foo = new char [10];  // allocate 10 bytes

And then delete it:

delete [] foo;

You use the brackets on the delete, as you used them with the new.

If you allocated one char you wouldn't use brackets. eg.

char * foo;
foo = new char;  // allocate a pointer to one byte
...
delete foo;
13
  • Actually, char content[]; doesn't give you a pointer - it gives you a buffer of zero length. Commented Jun 15, 2016 at 9:48
  • Thanks for your help. Can you explain to me why it was making a new variable content and not using the class variable?
    – Killerb81
    Commented Jun 15, 2016 at 11:21
  • Another question regarding your answer: In the destructor, when you delete the pointer to the array, does this free up the memory allocated to the array? It's now free to be assigned again by something else? Or does it sit there still initialized as that array, not being able to be used again?
    – Killerb81
    Commented Jun 15, 2016 at 15:28
  • On this page: link they show creating the dynamic array the same way as you did, but when they delete it, they delete pointer and then delete[] pointer. Did you leave this out? Or is this necessary?
    – Killerb81
    Commented Jun 15, 2016 at 15:42
  • Sorry, one more edit.. I made a mistake, on the link posted above they say to use delete this way: delete pointer if it was a single element allocated with new or delete [] pointer to release memory allocation for arrays of elements allocated with new and a size in []'s. So in my case I want to use delete [] content_? Thanks again!
    – Killerb81
    Commented Jun 15, 2016 at 17:43
2

Many programmers have a disjoint between a pointer and an array. Here's an explanation.

Here is a variable of type char, initialised with the value 'A':

char c = 'A';

'A' has the ASCII value of 65, so that means that there's a byte in memory that holds the value 65. Let's say that byte in memory is at address 0x2000. If you were to add 1 to that byte, it would become 66, and if you were to print it as a character, it would print B (ASCII 66).

Here is a pointer to a char, initialised with the address in memory of c:

char *p_c = &c; // Get the address of c into p_c

[Note that char *p_c = c; would be invalid: you can't initialise a pointer with a char! That'd give p_c the value 65 - not what you want!]

In Arduino, p_c represents two successive bytes of memory that together store the address of a char: in this case the value 0x2000. Where those two bytes are doesn't matter for this explanation - but trust that they too have an address...

If you were to change c into a 'Z', that would change c's contents, but not p_c's (it would still be 0x2000). But if you were to use p_c to look at the byte it was pointing to (c), then you'd see the 'Z'.

  • If you were to do a printf("%c", c); you'd get a 'Z'.

    %c means "I've got a character as the next variable in the printf() list".

  • If you were to do a printf("%c", *p_c); you'd also get a 'Z'.
  • If you were to do a printf("%c", p_c); you'd get horrible results.

    You lied to printf() - you didn't give it a one-byte character, you gave it a two-byte pointer!

Now let's move on to arrays.

Here is a variable of type char [], initialised with the value "John".

char s[] = "John";

'J' has the ASCII value 74, 'o' 112, 'h' 104, and 'n' 111. That means that in memory there are four sequential bytes with the values 74, 112, 104, 111. Let's say that those bytes are at addresses 0x3000 through 0x3003.

And here is another pointer to char, this time initialised with the address in memory of s.

char *p_s = s; // Point to s with p_s

I hate that the C language allows the above. It is one of the roots of the whole array/pointer confusion. It kind-of implies that p_s somehow gets all of "John" assigned to it. Nope!

A better statement would have been:

char *p_s = &s; // Initialise p_s with the address of s // ILLEGAL!

That gives a MUCH better idea of what's happening - alas, it's not how it's done...

But also note that neither of the above could possibly store 0x3000-0x3003 (the addresses for all of s), so they have to be satisfied with only storing the start of the string: p_s will get the value 0x3000.

Note that s does NOT have the value 0x3000 in it. That is its address, like 0x2000 was c's address (it too doesn't have 0x2000 in it). But p_s does have 0x3000 in it - and don’t forget that p_s also has its own address (I won't bother with that here).

And this is where the "equivalency" (they're not) between a pointer and an array starts to get people confused:

  • s[2] = 'h'; and p_s[2] = 'h'; do the same thing - but in different ways!
    • The first accesses 0x3002 directly.
    • The second needs to find out that p_s is 0x3000 and add 2 to it before it can access 0x3002.
  • *(p_s + 2) = 'h'; is yet a third way to do the same thing.

So now let's printf() these new variables:

  • printf("%c", *s); wouldn’t compile.

    s is not a pointer, so you can’t dereference it with *.

  • printf("%c", *p_s); would print the character at where p_s was pointing.

    This would print out J.

  • printf("%c", s[0]); would print the character at s[0].

    This would print out J.

  • printf("%c", s); would give horrible results.

    Again, you lied to printf()!

  • printf("%s", s); would work (but see below).

    %s means "I've got a sequence of characters that I'd like you to print. I've provided the address of the first character."

  • printf("%s", p_s); would be identical to the previous one.

    After all, the same address value was passed!

  • printf("%s", *p_s); would give horrible results.

    Again, you lied to printf()!

But note something: you've given printf() the beginning of the string, so it can start printing. How does printf() know where the end of the string is? You didn't provide the number 4; it's not stored anywhere; how can it know when to stop?

That's where a C convention for strings steps in. By convention, when the compiler sees a string "between quotes", it stores the ASCII values of the characters, and then follows them with a 0. Not an ASCII '0' (with value 48), but an ASCII NUL (with value 0). Thus if you looked at address 0x3004 (the byte directly after John), you'd see a 0 in that memory byte. That's how printf() knows to stop printing - and yes, many a bug has resulted from forgetting to maintain a NUL at the end of a sequence of chars!

That then leads me to the sizeof() function

sizeof() always returns a constant at compile time using information that the compiler then knows about:

  • sizeof(c) would return 1.
  • sizeof(p_c) would return 2.
  • sizeof(p_s) would also return 2.

    It is after all merely a pointer too.

  • sizeof(*p_c) would return 1.
  • sizeof(*p_s) would also return 1.

    It's only pointing to a single char...

  • sizeof(s) would return 5.

    The compiler knows what s is: an array of 5 bytes (including the final NUL).

  • Given a pointer, there is no (native) way to find out how large the buffer it is pointing to can be.

OK: so if you've got a char [] you can find out its size. Note that that is its maximum size - the string inside it could be a lot shorter! If you did the following:

s[3] = 0; // Overwrite the fourth character (arrays start at 0)
printf("%s", s);

[Note a better version of the first line would be s[3] = '\0'; which means "store the ASCII character 0 (NOT '0')" instead of "store the number 0".]

you'd get Joh printed out. Note:

  • sizeof(s) remains 5;
  • You can't get the length with sizeof(*p_s);
  • The string inside it is only 3 characters long.

So... if you've got a string buffer, or a pointer to one, how do you know how long it is? Count the number of characters until you reach a NUL - and there's already a function to do that for you: strlen().

Which now leads to the final point about pointers: dynamic allocation of memory.

The array s above was sized by the compiler at compile time: it could count the characters, add one for the NUL, and reserve the right number of bytes. You could also have written:

char s[10] = "John";

which would have reserved 10 bytes and only initialised the first 5 of them. The following would be an error:

char s[3] = "John"; // Buffer too small!

Scarily, char s[4] = "John"; works... (The compiler says to itself "No room for the NUL?! Ah well...”) Better never pass that string to strlen() or printf() then!

What if you don't know at compile time how big a string is going to be? If you know at runtime, you can ask the system for a lump of memory from the heap - a reserved area that can have chunks taken from it and returned as required. There are two ways to do this:

char *p_b = (char *)malloc(size); // Where size is a variable
char *buffer = new char[size];    // Grab 'size' new chars

The first calls malloc(), passing in the number of desired bytes. If malloc() can give those bytes it will reserve them and return a pointer to them. If it can't it will return 0. You should always test the return value! If not, you'll corrupt things horribly...

The second uses the more sophisticated new[] syntax, which is more object oriented. Notice how malloc()'s return value needed to be turned into a (char *)? That's because it doesn't know what type of pointer it's returning, so it just picks void *. new does know, so it returns the correct type - but it still may return 0 if there's no room!

Once you've got either pointer, you use them just like p_s before. There is nothing stopping you from accessing outside the boundaries of the buffer - just like there isn't for p_s! Be careful!

Oh, and once you've finished with the lump of memory, you need to return it to the heap:

free(p_b);
delete [] buffer;

If you used malloc(), return it with free(). If you used new[], return it with delete[].

And all of the above is why they invented the String object.

It manages the memory; keeps track of the string's length; lets you access and modify individual characters; lets you resize the string as needed; and gets rid of the used memory once you're finished with it. No wonder people start to use String syntax when they see string pointers...

But Strings are only useful for chars. Arrays and pointers can be of and to anything - including Strings and (GASP!) pointers! Uh oh - here we go again...

4
  • That should be a chapter in a text book!!! Thanks @John.
    – Killerb81
    Commented Jun 16, 2016 at 3:55
  • @Killerb81 probably multiple chapters in a text book ;)
    – aaa
    Commented Jun 16, 2016 at 6:35
  • Thanks, people. I've proposed it as an entry in the Tag Wiki for pointer. We'll see... Commented Jun 16, 2016 at 6:41
  • I forgot to include what would happen if you wrote printf("%s", p_c); . Needless to say, you'd get a good result - and then horrible ones, as it continued to print characters until it finally got to a NUL... Commented Mar 11, 2020 at 15:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.