C++ Questions

Pages: 123... 12
1) Why does "&myChar[0]" act differently in 2 different contexts?

 
char* pointer = &myChar[0];		//ALSO WORKS!!! 


Above, it actually return the address of the first element of the array.
Below, I expected it to print the address, but it prints the entire array. Does this have to do with how the original implementation of C language used chars and string arrays?

 
cout << &myChar[0] << endl;	//Hello 



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
using namespace std;

int main()
{
	char myChar[6] = { 'H', 'e', 'l', 'l', 'o', '\0' };
	//char* pointer = myChar;		//WORKS!!!
	char* pointer = &myChar[0];		//ALSO WORKS!!!

	cout << myChar << endl;		//Hello
	cout << &myChar << endl;	//00000030DDEFF744  Array start
	cout << myChar[0] << endl;	//H
	cout << myChar[1] << endl;	//e
	cout << &myChar[0] << endl;	//Hello

	cout << pointer << endl;		//Hell0
	cout << *pointer << endl;		//H
	cout << *(pointer + 1) << endl;	//e
	cout << *(pointer + 2) << endl;	//l
	cout << (const int*)pointer << endl;	//00000030DDEFF744   =  &myChar 
	cout << &pointer << endl;			//00000030DDEFF768    ptr itself

	return 0;
}
/*
OUTPUT:
Hello
000000F3C534F774
H
e
Hello
Hello
H
e
l
000000F3C534F774
000000F3C534F798
*/




2) Is there a way to prove something was "inline"'d or see if it actually happened? Same with constexpr, is there a way to see or prove it?
1) It's the stream << operator that has been defined to work differently for char*. It's indeed inconsistent but the reason for this is because char* are so often used to handle strings (especially in C). It's the overload that gets called when you pass a string literal.

 
std::cout << "abc"; // you wouldn't want this to print an address, would you? 


2) The easiest way to prove that a function was inlined is by looking at the assembly.

To make sure an expression gets evaluated at compile time you should use it to initialize a constexpr variable. You can also be sure it gets evaluated at compile time if you use it in some other context where a compile-time constant is required (e.g. in a template argument list). For mutable global variables you can use the constinit keyword to make sure the initialization expression is evaluated at compile time.

Note that there are compiler-specific limitations (that can often be configured by compiler flags) when it comes to compile-time evaluation of constexpr functions so be careful when forcing it because it will lead to a compilation error if it exceeds those limitations. This is not much of an issue if you control the compiler/settings that is being used but if it's part of some library which gets compiled with many different compilers where you cannot control what settings are being used then it might be something to think about.
Last edited on
<< of char * is pure c++ (C does not use the << operator for output, it uses printf function calls and similar). C++ code/compilers readily accept C code both as source files (code.c) and in the middle (you can use a printf in c++ if you want to). Because of how C uses char arrays as 'strings' and because you can easily intermix the two languages, for ease of use, C++ overloaded the << for C style strings. It is also for historical reasons, as C++ did NOT have a string object until very late in life so in the early days of C++ we also used char arrays/pointers as strings just like in C. For the same reasons (legacy code, intermixed with C code, etc) you can get a char* from a C++ string object as needed if you want to pass it to C code or older style C++ code.

Note that the name of an array is the pointer.
so char x[] = "c++ strings are tolerable now";
char * cp = x;//same as &x[0] which is a bit wordy

Thanks guys, I appreciate it!

So when you send a string literal, "abc", this actually sends a const char* to the first element of the array and the stream << operator has an overload for a char*. Also, when you send "&myChar[0] it is like sending a pointer that points to the address of the 1st element which is picked up by the overloaded stream << operator as a char*.


Recap:
1A) "myChar" is really a pointer to the 1st element(char*), so cout prints "Hello"

1B) "myChar[0]" is the value at element [0] location, so cout prints 'H'

1C) "&myChar[0]"..."myChar[0]" is the value at element [0] location, but now add "&" and it becomes the address of that location, which is picked up by overloaded << operator as a char*, so cout prints "Hello"

1D) "&myChar" is the address where element[0] resides.

Is the following the right verbal breakdown for the latter 1D,"&myChar"?
"myChar" is really a pointer to the 1st element(char*), now add the "&" and it says now give me the address of the pointer itself, but since there is no real pointer in the traditional pointer sense and the pointer name is itself the 1st element of the array...that it then retrieves the address of the 1st element of the array. Is that the right wording and the way to see it?

It is not correct to say that myChar HAS A POINTER added or attached to it, since then it will have an additional 8 bytes for pointer storage. Rather than, it is better to say that the "myChar" name itself is the pointer, which is itself the first element of the array?

BUT, I am also left to wonder now how this "&myChar" is also not seen as a char* from the stream << operator, since it has the address of the 1st element?

I am trying to picture how in the world they accomplished this on the other end? If you had to create a custom program with some overloaded operators for "&myChar" and "&myChar[0]" to do 2 different things, would you be able to? They both have addresses.
eh? I am not entirely sure I even get your question. but...
if in your question, myChar is a char*, then &myChar is a DOUBLE POINTER of type char**, which is, one way of looking at it, an 'array of c-strings' (possibly, most common use of such).

Eg argv is type char** but often written another way (as an unsized array of char*). Its the same thing, but as you know argv is multiple arguments from the commandline, each a c-string ... rolled into an 'array' or pointer grouping of c-strings... right?

you can keep taking the address of things. Nothing other than common sense stops you from having a type char*********** for example.

arrays and pointers are conceptually, and in many ways, syntactically overlapped very heavily in C and lower level C-like C++ (raw pointers, C strings, pre-STL c++ stuff or like special embedded C++ where STL isn't supported). If this is really important to you, and its still confusing, keep asking, but also consider reviewing a crash course in C language on pointers and string handling.

Note that string literals may be optimized away in the assembly without all the pointer nonsense. Raw assembly addresses are much like C pointers, and this is where the array overlap really comes into play ... but you will be off in the weeds playing in assembly. Just keep firmly in your mind that C and C++ are expressing your idea, and the compiler and hardware may or may not be doing exactly what the language indicates when it comes to constants and address lookups, for performance reasons. EG in c++ you often have a pointer variable, so you might expect an integer that stores a value that is a place in memory where something lives, but your optimizing compiler may just inject the target memory address directly rather than fetch it from an integer first -- its not necessary in all cases...
1A) "myChar" is really a pointer to the 1st element(char*), so cout prints "Hello"

myChar is an array. Arrays are not pointers but they easily decay into pointers when you use them (like an implicit conversion).

1
2
char myChar[] = "Hello";
char* ptr = myChar;

In the above code myChar is a char array of 6 elements while ptr is a pointer to char.

If you were to print the size of these variables you would see that they are different.

1
2
std::cout << sizeof(myChar) << "\n"; // prints 6
std::cout << sizeof(ptr) << "\n"; // prints the size of a pointer (usually 8) 


1D) "&myChar" is the address where element[0] resides.

It has the same address but the pointer type is different.

&myChar gives you a pointer to the array. The type is char(*)[6] (i.e. pointer to char array of size 6).

&myChar[0] gives you a pointer to the first element. The type is char* (i.e. pointer to char).

1
2
char (*ptr_to_array)[6] = &myChar;
char *ptr_to_first_elem = &myChar[0];


The reason why this is so confusing is because the rules has been intentionally designed to allow much the same syntax for arrays and pointers to the first element in arrays because it's so very common to use pointers when dealing with arrays.

1
2
3
4
5
6
7
// The intention is to allow you to use arr as if
// it were an array even though it's a pointer.
int* arr = new int[10];
for (int i = 0; i < 10; ++i)
{
	arr[i] = i + 1;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// It might look like the first parameter arr 
// is an array but arr is actually a pointer.
// Replacing `int arr[]` with `int* arr` has
// the exact same meaning to the compiler
// in this context.
void print_array(int arr[], int size)
{
	for (int i = 0; i < size; ++i)
	{
		std::cout << arr[i] << "\n";
	}
}

int main()
{
	// Passing a pointer to the function works fine!
	int* arr1 = new int[3]{1, 2, 3};
	print_array(arr1, 3);
	
	// Passing an array also works fine
	// (because it decays into a pointer)
	int arr2[] = {7, 8, 9};
	print_array(arr2, std::size(arr2)); 
}
Last edited on
The type is char(*)[6] (i.e. pointer to char array of size 6)


Don't confuse this with char*[6] which is an array of 6 elements of type pointer to char.

Also uint8_t and int8_t are usually just type defs for unsigned char and (signed) char. cout << will therefore usually treat unit8_t and int8_t as a char.

If you do want to display the address of a type char* then first cast to void*
Last edited on
These things I have memorized and know the outcomes when typing them in, but the full scope of understanding escapes me with some details and that is why I ask rather than to have it linger. Until I finally read somewhere else the explanation to gain further insight. As I am sure I will get there anyways given time, but you guys have helped me fast track it.

Below, (Sam's, p196).
 
int myNumbers[5];//Static array of 5 integers 



You tell the compiler to allocate a fixed amount of memory to hold five integers
and give you a pointer to the first element in that array that is identified by the name
you assign the array variable. In other words, myNumbers is a pointer to the first element
myNumbers[0].
This program demonstrates that an array is a pointer to the first element in it.


So here it states that it "is" a pointer and not "like" a pointer. So the first thing that came into my mind is that it must reserve an 8 byte memory location, since it "is" a pointer. But no, there is no evidence of this additional memory. So then I will ask, so where is this pointer address stored? And then you will tell me that the name is itself the pointer and is itself the address to the first element.

So then really it acts like a pointer in the syntax of the code and usage, but in memory and compiled code it behaves like a reference, where there is no additional memory hit for the pointer value...because the array name itself is the pointer to the first element of the array.

And, I might as well pose this statement here, or question if you refute me.
3) Reference "&" is just an alias to what it refers to and during compile time it gets replaced with the actual thing it was referencing/referring to. And "sizeof("&myRef")" will always give the sizeof whatever it was referencing, and so it is proof there is no additional memory hit for the reference usage. This ties into my statement about arrays above, because there is no additional memory hit for the pointer of the array name.

You have helped me understand though, that "&myChar" is like saying give me the address of the pointer (**, pointer to a pointer) and on the receiving end (func or operator) it can be overloaded for char**:

So that is how it can be overloaded on the other end. Sorry, I don't have too much experience with pointers of pointers, but I do see them when you guys refer to them and I have to investigate them further at some point. That was my missing link to fully understanding.

But now I can easily see that you can overload like this, where as before I wondered how this was possible because in reality they both are just addresses. Somehow the compiler can detect and sniff out a pointer to a pointer and make that distinction.

1
2
operator <<(char**)     //Can be overloaded for &myChar
operator <<(const char*)     //Can be overloaded for &myChar[0] 








Some people try to teach that arrays are pointers, either because they don't know better, or because it simplifies the explanation and is "true enough" to a beginner, but the truth is that arrays are not pointers.
Last edited on
As I said above, arrays and pointers are intertwined heavily but they are distinct things. The name of an array is translated to a pointer FOR you, by the language syntax, as a shortcut to saying &array[0] all the time. Because of this automatic translation, the name of an array IS a pointer in syntax, but, the array is NOT a pointer. Its just a syntax shortcut, similar to how x+=3 is shorthand for x = x+3.

here is the difference, very carefully:
a pointer is just an integer variable. That is all it is. The integer it holds has a special meaning: it is a location in your computer's memory. You can use a few special functions on it to allocate and release memory which gain control of or release control of computer resources at run-time. Pointers to existing data cannot use the special functions (eg you cannot release the memory from a pointer crafted by taking the name of an array). A pointer variable assigned the array name but then hit with a new() reassigns the pointer to a different value (location in memory). An array name used directly with new() is an error.

an array is a solid block (each byte of data in the array touches another byte of data in the array unless the array is only 1 byte in size) of bytes that hold one or more pieces of data in array-type chunks. It has no special functions for memory handling because the memory handling of an array is automatic and trivial (its just a program memory stack push).

Where they overlap is:
accessor syntax. both can use [] to get to a value.

function parameters: you can pass arrays into a pointer parameter easily, but the function must not to try to use memory functions on it as said above.

general layout: a raw pointer allocated in a block and an array are almost identical in memory and raw access to them is identical.

c-string functions: these work fine on char arrays: I never used pointers, always arrays, for C strings.

and similar things as above .. there are more, but generally speaking its more of the same.
Last edited on
No, I understand guys. My last post was designed to show where I was coming from and why I was questioning, but you have all answered my question.

If I had to label it I would say that the myChar array "acts" like a pointer, but is not a pointer. It is even more than a pointer, because the array also knows its size as well, meanwhile a pointer doesn't.
The name of an array is translated to a pointer FOR you, by the language syntax, as a shortcut to saying &array[0] all the time. Because of this automatic translation, the name of an array IS a pointer in syntax, but, the array is NOT a pointer.

I don't like this explanation because it doesn't happen all the time. There are many situations where the fact that it's an array and not a pointer makes a difference.

- When using sizeof(array).

- When binding a reference, e.g.
1
2
int arr[5];
int (&ref)[5] = arr;

- When passing it to a function (template) such as std::size.

If the name of the array was a pointer then it should work the same if you instead used a pointer in these situations but that is not the case. The first one would return a different value and the other two would fail to compile because you cannot bind a pointer to an array reference.

It's better to think about it as an implicit conversion. You can often use an int where a double is expected but I don't think anyone would claim that the name of an int variable is a double. It's essentially the same with arrays.
Last edited on
I agree with that.

In many places, arrays are on, I tend to see the assembly in my head.. to me an array is an address, and you jump from (or add to) that base address to an offset (which may be zero) to get the item you want... which is where you end up in the C/C++ but the high level language does have a little more to it, as your examples show.
I thought I might try a pointer to a pointer for myself today. I am not ready to read about it yet, but thought let me see if I am able to demystify this a bit from trials. I don't see myself using it anytime soon, but where will you use this on? Is this used often?

I can see it used on such things as when one had to insert new instance from the first pointer and use the 2nd pointer to go searching for something.
One can also make 2 independent pointers and keep track of them that way, if one is not concerned about speed and does not yet have an intuition and full experience with them, but obviously you lose the religious tracking between the pointers.

In the below code, is there a way to tame and control the comment tabs?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <iostream>
using namespace std;


int main()
{
	int varX{ 23 };

	int* pX {&varX };
	int** ppX {&pX };
	
	//Values:
	cout << varX << endl;									//23
	cout << *pX << endl;									//23
	cout << **ppX << endl;									//23

	//Address of varX:
	cout << "varX address = \t"	<< &varX	<< endl;		//00000093FD4FF5A4
	cout << "varX address = \t"	<< pX		<< endl;		//00000093FD4FF5A4
	cout << "varX address = \t"	<< *ppX	<< endl;		//00000093FD4FF5A4

	//Address of the pointers:
	cout << "pX address = \t"	<< &pX		<< endl;		//00000093FD4FF5C8
        cout << "pX address = \t"	<< ppX		<< endl;		//00000093FD4FF5C8
	cout << "ppX address = \t"	<< &ppX		<< endl;		//00000093FD4FF5E8

	cout << "*****************************" << endl;

	
	char C[6] { 'H', 'e', 'l', 'l', 'o', '\0' };

	char* pC { &C[0] };
	char** ppC {&pC};

	//C-string Values:
	cout << C	<< endl;									//Hello
	cout << pC	<< endl;									//Hello
	cout << *ppC << endl;									//Hello

	//Address of C:
	cout << "C address = \t" << &C					<< endl;//00000093FD4FF604
	cout << "C address = \t" << (unsigned int*)pC	<< endl;//00000093FD4FF604
	cout << "C address = \t" << (unsigned int*)(*ppC) << endl;//00000093FD4FF604

	//Address of the pointers:
	cout << "pC address = \t" << &pC	<< endl;			//00000093FD4FF628
	cout << "pC address = \t" << &*ppC	<< endl;			//00000093FD4FF628
        cout << "pC address = \t" << ppC	<< endl;			//00000093FD4FF628
	cout << "ppC address = \t" << &ppC	<< endl;			//00000093FD4FF648

	return 0;
}
/*
OUTPUT:
23
23
23
varX address =  00000093FD4FF5A4
varX address =  00000093FD4FF5A4
varX address =  00000093FD4FF5A4
pX address =    00000093FD4FF5C8
pX address =    00000093FD4FF5C8
ppX address =   00000093FD4FF5E8
*****************************
Hello
Hello
Hello
C address =     00000093FD4FF604
C address =     00000093FD4FF604
C address =     00000093FD4FF604
pC address =    00000093FD4FF628
pC address =    00000093FD4FF628
pC address =    00000093FD4FF628
ppC address =   00000093FD4FF648
*/
Last edited on
You forgot (for completeness):
1
2
cout << "pX address = \t" << ppX	<< endl;
cout << "pC address = \t" << ppC	<< endl;


And did not venture into the deep:
1
2
3
4
5
6
int**** A = nullprt;
A    = new int***;
*A   = new int**;
**A  = new int*;
***A = new int {42};
// Who is where now? 
Thanks, I added it & updated it.
Not sure I am ready to swim that deep today.

Please don't tell me that those are actually used.
C is known to use 3-4 deep pointers to represent multi dimensional arrays of up to 4 dimensions. Its very rare to need 4 or more; 3 and below are much more common to represent 3d space and whatnot, 4d can be 3d space over time.

c++ does not typically do this, though vector<vector<vector<int> > > isn't that much prettier. C++ most people avoid raw pointers without exceptional requirements or very compelling reasons. Ive only done it a time or two, and one of those that I recall well was just organization of data, where the boss had 10 managers under him, each manager had 3 assistants each with 20 people ... something like that where it was just a reshape of a bunch of people stored in an array with the pointer connectivity mess applied on top to show who was grouped under what branch.

I mean you were asking about text.. a pointer full of chars is a 'string'. A bunch of strings is a sentence. A bunch of sentences is a paragraph. A bunch of paragraphs is a chapter. A bunch of chapters is a book.... but would you really need to do it with pointers? Not in c++. In C, probably, but even in C you would use structs or even just typedefs to organize it cleaner than just a raw char ******** thing
Last edited on
Trying to cruft a multi-dimension std::array container is really awful and ugly.

https://www.learncpp.com/cpp-tutorial/multidimensional-stdarray/

Yeah, the dimensions in a 2D std::array are "reversed"!

Maybe it is too much to ask but should the ISO Committee consider making at least 2D be a part of the stdlib for the C++ containers? C++17 added the deduction guides so a 1D container doesn't need to explicitly declare the type.

I guess the best thing is some form of using statement(s) is what to do, since a multidimensional container is a custom job.
Maybe it is too much to ask but should the ISO Committee consider making at least 2D be a part of the stdlib for the C++ containers?
They're getting closer.

C++23 introduced std::mdspan, which lets you view a contiguous sequence as an arbitrarily multidimensional one. It seems a bit over-generalized but potentially still helpful.
C++23 also allows operator[] to have multiple arguments.
Last edited on
Pages: 123... 12