C++ / Arduino understanding the usage of uint8_t and *

Can someone explain to me what's going on in this code block? Specifically on line 3. I have a hunch the * before ptr is significant. And (uint8_t *) looks like a cast to a byte... But what's up with the *? It also looks like r, g, and b will all evaluate to the same value.

case TRUECOLOR: { // 24-bit ('truecolor') image (no palette)
  uint8_t  pixelNum, r, g, b,
          *ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS * 3];
  for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) {
    r = *ptr++;
    g = *ptr++;
    b = *ptr++;
    strip.setPixelColor(pixelNum, r, g, b);
  }

I work primarily in C#.

Upvotes: 0

Views: 2072

Answers (4)

user4581301
user4581301

Reputation: 33982

Used in a variable definition, the * means ptr is a pointer. The value it stores is an address in memory for another variable or a part of another variable. In this case ptr is a pointer to a block of memory inside imagePixels and from the names of the variables involved it's a line in an image. Since the type is uint8_t, this is taking whatever imagePixels is and using it as a block of individual bytes.

Used outside a varable definition, the * takes on a different meaning: dereference the pointer. Go to the location in memory stored in the pointer and get the value.

And yeah, * can also be used for multiplication, upping the code-reading fun level.

Incrementing (++) a pointer moves the address to the next address. If you had a uint32_t * the address would advance by 4 to point at the next uint32_t. In this case we have uint8_t, so the address is advanced one byte. So

r = *ptr++;

A) Get value at pointer.

After A) Advance the pointer.

After A) Assign value to r.

Exactly where the "advance the pointer" stage goes is tricky. It is after step A. In C++17 or greater it is before "Assign the value" because there is now a separation between the stuff on the right and the stuff on the left of an equals sign. But before C++17 all we can say is it's after step A. Search keyterm: "Sequence Points".

g = *ptr++;
b = *ptr++;

Do it again, get and assign the current value at ptr, advance the pointer.

strip.setPixelColor(pixelNum, r, g, b);

From the naming I presume this sets a given pixel to the colours read above.

You can't just

strip.setPixelColor(pixelNum, *ptr++, *ptr++, *ptr++);

Because of sequencing again. There are no guarantees of the order in which the parameters will be computed. This is to allow compiler developers to make optimizations for speed and size that they cannot if the ordering is specified, but it's a kick in the teeth to those expecting left-to-right resolution. My understanding is this still holds true in the C++17 standard.

OK. So what is this doing?

There is a big block of memory from which you want one and only one line.

*ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS * 3];

pinpoints the beginning of that line and sets it up to be treated like a dumb array of bytes.

for(pixelNum = 0; pixelNum < NUM_LEDS; pixelNum++) {

Generic for loop. For all the pixels on the line of LEDs.

r = *ptr++;
g = *ptr++;
b = *ptr++;

Get the colour of one pixel on the line in the standard 8 bit RGB format and point at the next pixel

strip.setPixelColor(pixelNum, r, g, b);

writes the read colour to one pixel.

The for loop will then loop around and start working on the next pixel until there are no more pixels on the line.

Upvotes: 1

Nathan
Nathan

Reputation: 21

MildlyInformed, I would need more code to run through it to explain it. One tool I found really, really useful though is the C visualizer. It's an online debug tool that helps you figure out what's happening in code by running you through step-by-step, line by line. It can be found at: http://www.pythontutor.com/visualize.html#mode=edit

Even though the URL talks about python, it can do C and a bunch of languages. I would have commented instead of posting an answer, but my rep isn't high enough. I hope this helps!

(I'm not affiliated with the above website, other than to use it occasionally when I'm baffled)

Upvotes: 0

Phil Kiener
Phil Kiener

Reputation: 778

The second and third line can be expressed more cleanly:

uint8_t pixelNum;
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t *ptr = (uint8_t *)&imagePixels[imageLine * NUM_LEDS * 3];

The first four variable declarations should be fairly simple, the fifth one is something C# does not have. It declares ptr as a pointer to a uint8_t. This pointer is set to the address of the value which is the imageLine * NUM_LEDS * 3th element in the imagePixels array. As this might be a different type (maybe a pointer to a char, who knows), this value is cast to a pointer to an uint8_t.

The next occurence of the asterisk (*) is in the for-loop body, where it is used as the dereference operator, which basically resolves a pointer to get the actual value.

Pointers 101

A pointer is like the street address of a house. It shows you where the house is so you can find it, but when you pass it around, you don't pass around the whole house. You can dereference it, meaning you can actually visit the house.

The two operators used in conjunction with pointers are the asterisk (*) and the ampersand (&). The asterisk is used in declarations of pointers and to dereference a pointer, the ampersand is used to get the address of something.

Take a look at the following example:

int x  = 12;
int *y = &x;

std::cout << "X is " << *y; // Will print "X is 12"

We declare x as an int holding the value 12. Now we declare y as a pointer to an int, and set it to point at x by storing x's address. By using *y, we access the actual value of x, the int that y points at.

Since a pointer is a type of reference, modifying the value via the pointer changes the actual value of the thing pointed at.

int x = 12;
int *y = &x;

*y = 10;

std::cout << "X is " << x; // Will print "X is 10"

Pointers 102

Pointers are a large topic, and I suggest you take your time to read about them from different sources if necessary.

Upvotes: 2

RSproule
RSproule

Reputation: 184

The asterisk(*) is the symbol for a pointer. So the (uint8_t *) is a cast to a pointer that is pointing to a uint8_t. Then within the loop, where the asterisk is prefixed to a symbol (ie *ptr) that is dereferencing that pointer. Dereferencing the pointer returns the data that the pointer is pointing to.

I suggest reading a bit about pointers as they are critical to understanding C/C++. Here is the C++ Docs on Pointers

Upvotes: 0

Related Questions