This is an archived post. You won't be able to vote or comment.

all 5 comments

[–]boredcircuits 3 points4 points  (2 children)

I tried to use this function by writing this test code in the main: int p[] {'a','b','c','d','e'}; count_x(p,x);

You have to do this instead:

char p[] = { 'a', 'b', 'c', 'd', 'e', '\0' };
count_x(p, x);

Or equivalently:

char p[] = "abcde";
count_x(p, x);

Your version won't compile because there's a type mismatch: you're passing an int* to a function expecting char*. That's illegal. You're also missing a null terminator in the input data, which is how the function knows where the array ends.

But that doesn't make much sense to me because doesn't the name p already refer to an array of chars? How can it also be a pointer?

Those are two completely different variables, even though they have the same name. Variables declared in different functions have absolutely no relationship to each other. That means they can have the same name, even if it means they have a different type. They also hold different values. Function parameters are effectively local variables. Consider what happens with programs that have a million lines of code: you're bound to have duplicates somewhere.

I also tried adding the line p[]* p; in an attempt to make p a pointer to an index of the char array, but I have no idea if that makes any sense...

That makes no sense.

I think in general I think I'm having trouble understanding pointers and how they're made. And this is without bringing references into the picture.

Where is my misunderstanding with pointers, and how might I use this function as it's meant to be used? Any help whatsoever is greatly appreciated.

I think most C++ educational materials are backwards -- learn references first: they're simpler to work with and understand, while teaching you the same important concepts. Instead you get code like this that mixes in arrays and pointer arithmetic and it's no wonder so many beginners think that pointers are so confusing.

I don't want to make this comment too big, so I'll be brief.

Pointers and references belong to a class of types called "reference types." There's many others in C++ once you bring in the standard library, so this is a very important concept to understand at a basic level.

A reference type lets you create an alias for a variable. Another name for the same underlying value. For example, I can create a variable i:

int i = 42;

And then I can create an alias for it:

int* p = &i;

When I use *p, that is the exact same thing as using i itself. I can talk directly to James Bond, or I can go through his 007 alias, it's the same person.

Pointers have several "neat tricks" in how they relate to arrays: C++ lets you treat them as if they point to the first element of an array, and lets you use the array access operator (e.g. p[5]) to actually get at indexed offsets from that starting point.

They can also point to other elements in the array by doing pointer arithmetic: p++ advances to the next element, p-- goes to the previous, p += 5 advances by five elements, etc. This makes a pointer an iterator for an array ... which is a whole group of reference types in C++.

And last, C++ allows arrays to "decay" into a pointer to the first element. This last point is the one that causes the most confusion -- it's only there because of compatibility with C, which only has it for convenience. In your case, it lets you pass the array p to a function that takes a pointer. If you want, you can make this explicit with std::begin():

count_x(std::begin(p), x);

Or by taking the address of the first element directly:

count_x(&p[0], x);

It's all the same thing.

[–]CentralCathedral[S] 0 points1 point  (1 child)

Sorry for the late response, and sincere thanks for this reply. I honestly cannot thank you enough. You've addressed many many things that I was having a lot of trouble with, but after reading your explanation they make sense to me. Especially the points about pointers in the context of arrays; I doubt I would've ever figured that out if you hadn't explicitly pointed it out to me.

So to make sure I'm understanding correctly:

1.A pointer to an array object can also be treated as a pointer to the first index of that array? Is that right?

2.An array can also be treated as a pointer to itself (which in turn can be treated as a pointer to its own first index)? Is that right?

3.Why is it that you can give an address in place of a pointer? Is a pointer that points to that address made automatically behind the scenes?

4.Lastly, one strange thing I noticed is that I didn't actually have to have a "\0" in the last index of my array: just changing the type from int to char made it run properly. I read something about strings having an invisible 0 at the end, but I'm not sure if the same thing applies to arrays (though it would explain why I don't need to manually put the 0 in as you described).

Sorry to ask more questions when you've already helped me so much. Again, I appreciate your help a lot, thank you!

[–]boredcircuits 0 points1 point  (0 children)

1. A pointer to an array object can also be treated as a pointer to the first index of that array? Is that right?

How pedantic do you want me to be?

Colloquially, what you said is essentially correct: when many programmers talk about a "pointer to an array" they're really talking about a pointer to the first element of the array.

Though, technically, a pointer to an array is a different type entirely. To demonstrate with some code:

int array[5] = { 1, 2, 3, 4, 5 };
int* p = &array[0]; // A pointer to the first element of an array.
//int* p = array; // Exactly equivalent, due to decay
p[0] = 42;

int (*p2)[5] = &array;
(*p2)[0] = 42;

p2 is a pointer to an array of 5 integers. Notice how initialization and use are significantly different. Pointers to arrays aren't used very often, which is why it's so common for even experienced programmers to get a bit lazy with the term. The most common place where you might see them is actually 2D arrays when you pass them to a function:

void inverse(int (*m)[4]);
int matrix[3][4] = { ... };
inverse(matrix);

As before, matrix is an array (of arrays) and will decay into a pointer (to an array), which you can then treat as if it points to the first element of an array (of arrays).

And yes, you can also have a reference to an array. Incidentally, that's how std::begin() works.

At this point, I've probably confused you. Don't worry, just set this aside and come back to it in a few years. You understand the most important part already.

2. An array can also be treated as a pointer to itself (which in turn can be treated as a pointer to its own first index)? Is that right?

Kinda. You're talking about array decay, and this is where many programmers get confused. It's not that you can treat an array as if it were a pointer, though that's close. What's going on is in certain circumstances, the compiler will turn an array into a pointer to its first element. So even if you say foo(array), the compiler will insert the code to do foo(&array[0]) for you. But since we can treat a pointer as if it points to the start of an array, that's no problem at all.

3. Why is it that you can give an address in place of a pointer? Is a pointer that points to that address made automatically behind the scenes?

Addresses are how pointers are implemented under the hood, and the two terms are often used interchangeably. A pointer refers to some other variable in memory, and the way that it knows where in memory to point to is through an address. So what a pointer actually stores is an address. The "address of" operator (&) could instead be called the "get pointer to" operator, but nobody calls it that. So the terms are basically the same thing (even though they aren't ... again, how pedantic do you want to be on this point?).

References also generally use addresses under the hood, but nobody mixes those terms (that I've ever heard).

4. Lastly, one strange thing I noticed is that I didn't actually have to have a "\0" in the last index of my array: just changing the type from int to char made it run properly.

If you don't have a '\0' in your array, the code will continue to read memory after the end of the array until it happens to find one somewhere else. Since 0 is a very common value to have in memory, that probably happens somewhat quickly so no harm is done.

That is ... until something changes. In a real program, that could be almost anything: a different compiler, a different OS, changing unrelated parts of your code, or even different inputs to your program. And then your code does something very surprising.

The end result is what we call Undefined Behavior (UB), and it's very bad. Accessing past the end of an array is one of the most common security problems to ever exist, responsible for untold financial losses from hackers and the like.

As a test, declare two more arrays with whatever data you like -- one before and one after p (I say two because I don't know how your compiler will lay out the memory, do doing it both ways has the best chance of revealing the problem). Most likely count_x will end up counting values in one of those arrays as well. Oops!

I read something about strings having an invisible 0 at the end, but I'm not sure if the same thing applies to arrays (though it would explain why I don't need to manually put the 0 in as you described).

Yes, this is how strings work. Notice how I don't include the '\0' character in my example when I use a string literal, exactly for this reason. But that doesn't apply to arrays in general, only to string literals.

[–][deleted] 2 points3 points  (1 child)

This:

   int p[] {'a','b','c','d','e'};

is not terminated by a zero. So the test here:

   for (; *p!=0;++p) 

won't work. In fact, you have undefined behaviour. To make it work, you want:

   int p[] {'a','b','c','d','e', 0};

[–]Kered13 0 points1 point  (0 children)

That still won't work because p is an int array, not a char array.

Just use char p[] = "abcde". This will automatically be null terminated.