you are totally right. i discovered this fact by myself when writing my own C compiler. At first I made a reference pointer to the array and later on realized I didnt need it. All I needed was the actual array and its address to deal with it. True!
I went to school in the late 80s and compilers was a core CS requirement. You wrote a compiler in the first semester and used it for all the projects in the next one. We also made breadboard projects to demonstrate multiplication and division using only logic gates. It seems like foundational CS is no longer taught and new engineers are robbed of any depth of understanding of crucial concepts. You can't optimize code if you have no idea how either the compiler or CPU will treat it. Had an engineer write a mail program to send to 30 mil customers. Kicks off job. Next day I ask how it went. Still running. How much is left? It sent 50k so far. Overnight? Yeah. Out of 30 million? Yeah. That will take two more years to finish: rewrite it. I want to wait until tomorrow to see if it finishes. True story.
@@user-zu1ix3yq2w once you take its value it becomes a pointer, but an array variable is really literally an "array": a fixed-size sequential list of objects in memory of a certain type. If you take the sizeof() of an array, you get the size of the full array object in memory. (From the video I am guessing that Huw would be surprised by that.)
@@butchdean it does not help that the video is wrong. An array is not equivalent to an address. It is a memory area of a certain size with an array structure imposed on it.
As a non-native English speaker, there is a chance I misunderstood you. However I'm pretty sure you made a mistake and explained this incorrectly. First off, I agree that the statement "An array pointer is a special type of pointer" is at least incomplete when it comes to explaining what they mean by "special". Even though an array name like str1 represents the address of the first element of the array, it can convert and becomes a pointer to the address of the first element of the array, when you use it as an expression. It is different than the Pointer the C-user creates, and probably therefore considered "special". I'm not sure. That aside, I completely disagree with the assertion made at 0:12. You say "An array is not a variable ...". To me a "variable" is a named storage of which you can modify the value. You showed 2 examples. First, you showed a "char*" named "str2" that you changed from "Goodbye" to "Good Day", and that worked. So the "char*" named "str2" is definitely a variable. Then you tried to change the value of an array named "str1" from "Hello" to "Good Day", but that didn't work. However, this doesn't mean the array named "str1" isn't a variable. It just means you used the wrong method to change its values. If you had used "str1[0] = 'B';", you would have changed the values of array "str1" from "Hello" to "Bello" and therefore the definition of what a variable is, would apply. I believe the logical mistake you made was limiting the concept of an array to it having a name or "array name" with a fixed address that doesn't change. That part is correct but doesn't capture the entire concept of what an array is. You use that constant address to access the values stored at and around that memory location. The address itself doesn't change, but the values stored in that memory are not fixed and can be changed. Therefore, and array is definitely a variable too. I think we agree that something about all the sentences at 8:14, 9:08 and 9:48 is either wrong or at least confusing. The first quote is like I said, if you use an array as an expression it becomes a pointer. The second quote is misleading, no matter from what aspect you want to correct it. If you want to change the word "variable" to "constant" instead of calling it a "special pointer" like before it would still be misleading. Again if you us the array as an expression it converts to a pointer pointing to the address of the first element. The third quote is correct but you have to read it very carefully to not arrive to the wrong conclusion as I think you did. The quote does not compare an "array" with a "pointer". It compares an "array name" with a "pointer". It could have compared an "array name" with a "pointer name" to say that both are constant, once defined. Or they could have compared "array values" with "pointer values" to say that both can be modified. But in this case comparing "array names" with "pointers" only shows that you have to apply a different methods to access and modify the values. Out of context the third quote is a bit stupid. Why even mention that "array name is not a variable"? Like all variable names, an array name is a constant identifier that refers to a specific memory location you can modify. Even if you can't "pa = a" or "pa++" like you can with pointers you can "str1[0] = a" or "str1[0] = str1[0] + 1" using the indexes. I hope there aren't too many mistakes. I rarely write this much English. ^_^
I will return to look at this in more detail. Put simply, an array compiles to address. It's not a pointer and it's not a variable. The array (i.e. the address) cannot be changed. The data stored at that address and sequential addresses can be changed. I can see that many programmers think of an array as a fixed list of typed data elements. That's not really true. In C, the array is a constant value (an address) at which elements of a certain size (but not necessarily of a fixed type) can be stored. The array (address) is constant. The data elements are not. That is different from arrays in higher level languages like C# or Java. In C, an array nothing more than an address. I can't explain more in this comment. But I will explain arrays in much greater depth in other videos soon. Best wishes Huw
I don't get all this debate. I've always seen arrays as a reserved memory with a defined (and immutable) number of elements that happen to have a (also immutable) starting address. The fact that you can reference an array with a pointer in certain situations is just a clever move by the creators of C.
Brilliant explanation. I am a mainframe assembler programmer since 1969 and your explanation is clearly visible in assembly. I can see how C programmers can get this very simple concept incorrect. Thank you for your concise explaination
The way it was described in the K&R book is the reason so many students learning C struggle with the subtle difference between pointers and an array. Arguably, both the 1st and 2nd edition's wording is still confusing for a lot of new learners. When thinking about things like this a bit, I tend to wonder how the world might have been different if some things while learning the C programming language were made more clear, and perhaps many problems with pointers were avoided altogether in the language design. A few more thoughts about how to explain the difference between array of chars and pointer to a char: - char * foo = "bar"; - Can be thought of as literally a pointer to a single "char", which the compiler treats as a null-terminated string, "bar\0" - It stores the address of that "char" in memory, and can be assigned another address later. The stack variable therefore contains the space needed to store an address of a null-terminated string of "char". If it's declared with an initial value, "bar\0", then the compiler places that into the binary at some location, and initializes the pointer variable's value so it points at that location. - char foo[] = "bar"; - Can be thought of as a "static"-_ish_ array of characters, allocated at compile time to the "static" string "bar\0" - It is known at compile time, and is "static"-ish in the sense that it won't change, or perhaps we can say it's "constant"-ish in the sense that it can't change. In other words, the compiler takes the code, sees an array of characters defined and fully known at compile-time (when initialized), and creates an address on the stack to hold that string. As a result of compilation, the array's address and value are the same, and it points to this location holding the string, "bar\0". I like the term "static" here because it is similar to how the compiler can statically compile objects and put them in the resulting binary. In the example, it's putting "bar" into the resulting binary as a "static" string. However, to avoid confusion with "static" do read the following... **Note:** I'm trying to be careful with terms here... saying "static"_ish_ and "constant"-_ish_ because both "const" and "static" are already keywords with overloaded meanings. So, although similar, it's not exactly the same meaning as "static" C keyword, as that implies "static" storage duration and defines behavior for an uninitialized array. Meanwhile, the array example instantiation in the video, and above has the implied "auto" storage duration which does not define what the contents of an initialized array would be. In other words, an uninitialized "auto" storage duration array has indeterminate contents, which could be garbage values, or whatever emergent behavior that the combination of operating system, machine and compiler implementation create. Luckily, in the case of the examples in the video and here, we are initializing both array and pointer to something... so we can sort-of gloss over that when trying to understand just this particular aspect of the language. (Alas, even my attempt to try and explain it is probably confusing)
Yep! It basically boils down to, Array: An address of an element of which the compiler sees as a single unit of sequential elements. Pointers: A value that simply represents the address of a (potentially as a struct) grouping of data currently stored in memory. Value -> Data Pointer -> Represents a Value Some people really have the hardest time apparently with wrap their head around everything being just numbers. But pointers are easy once it clicks. The CPU sees RAM as a sequence of bytes. That right there can help build an intuition of what you described.
@LearnWithHuw In my case, I transitioned in the opposite direction. I began writing in raw hex, no assembler, then assembler, then C, then C++. The whole thing about pointers and addreses just seems so natural having started at the bottom.
I think you’ve put your finger on the problem, but I don’t see that the explanation resolves the confusion. To say that “an array is an address” is almost as confusing and wrong as to say that “an array is a pointer”. An array is an object. Addresses are not objects; they are values. So arrays are not addresses. To say that they are is a category error. The reason people get confused is that when we write programs we often write the name of an object where a value is needed. The usual way this is resolved is to fetch the value stored in the object. If I have “int x;” then I can write x where an int value is needed, e.g. x = x + 1; On the left x is used to name the object, on the right it is also used to name an object too, but what is needed is a value, so there is an implied fetch. There is another way to convert an object to a value and that is to take its address: “&x”. Addresses are values. When we have “int a[10];” and we write “a” where a values is needed, e.g., when you pass it as an argument to printf, it evaluates to a value, namely the same value as &a[0] evaluates to. This is an implicit conversion. A good C programmer should understand when they are looking at an implicit conversion. We don’t say that an int “is a” double; we say that an int “implicitly converts to” a double. If anyone still thinks that an array is an address, ask yourself how big (in bytes) is an address. Now write a program to print the values of “sizeof(a)” where “a” is a large array.
C certainly has objects. (What it lacks is “class”.) Maybe take a look at section 6.3.2.1 of the ISO standard that explains when “an expression that has type ‘array of /type/’ is converted to an expression with type ‘pointer to /type/’ that points to the initial element of the array object”. Note the last two words. Also the word “converted” All the best.
@@LearnWithHuw The C standard is littered with the word object. Maybe there are no classes in C but there sure are objects. The C standard defines an object as a "region of data storage in the execution environment, the contents of which can represent values". The C standard describes an array type as follows: > Any number of derived types can be constructed from the object and function types, as follows: > > - An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. The element type shall be complete whenever the array type is specified. Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T, the array type is sometimes called “array of T”. The construction of an array type from an element type is called “array type derivation”. This is from C17, but the wording is similar or the same in both C11 and C89. As you can see a value of array type is a set of objects of the arrays element type. Elsewhere it says that: > Except when it is the operand of the sizeof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type “array of type” is converted to an expression with type “pointer to type” that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.
I had thought until yesterday that x[i] is always equivalent to *(x+i). But this is not the case when x identifies an array with “register” as its storage class, the latter may be well defined, but the former cannot be. This shows that not only are arrays not addresses, but some arrays and their elements don’t even have addresses.
furthermore, sizeof(array_thing) returns the size of the array and sizeof(ptr_thing) returns the size of the ptr. This also trips people up. I think a lot of the confusion comes from the fact that arrays decay into pointers when passed into functions. Another source of confusion is the interchangeability of array index notation and ptr math on both arrays and pointers.
Great video. Will pass it along, saving me from explaining it. Thanks! I look forward to scoping your channel. I like exactness and accuracy, and great to see someone who believes in such. Subscribed. Cheers
It gets more confusing when a parameter of a function is declared to be an array, because that gets adjusted to become a pointer. I.e void foo(char *bar); and void foo(char bar[]); are the same thing.
Useful, thank you. Out of interest I compiled the your code with gcc under linux. For anyone wishing to see what the compiler does generating assembler use "gcc test.c -O0 -o test -Wa,-adhln=test.s -fverbose-asm -fno-stack-protector" and "objdump -d test" and "hexdump -C test". I'm ten years out of practice with Intel assembly, hence mostly confused.
It's always a good idea to take a look at a disassembly (if you can understand it!). Maybe that's something I'll do in another video. Thanks for the comment. Huw
Good suggestion. I often do the same but I often crosscompile for another architecture which I prefer for reading. I disable -g because -g makes for pretty unreadable compiler output. As for the K&R C book mentioned in the video. While I learned C from those books myself, I don't recommend them anymore. The C language has gone through quite an evolution over the years, several versions of the ISO C standard have been publshed over the years. The second release of the K&R book is documenting the C standard from 1990 and is commonly referred to as ANSI C. As partially described by the video the books are a little inaccurate in their language. Which is a pain if it's about the finer details but is probably making the books more readable. Compare to the actual ISO C standard documents which most people would refuse to read at gun point ;-) If after all you decide to go for the K&R book, to those who speak German I suggest the German translation published by Hanser. The translators were very careful to be more precise than the original. Then again that doesn't make its content newer than the original K&R it was translated from. Finally K&R is a book for people who already know programming at least somewhat. It's not a “Programming for Utter and Total Dummies™”. Total newbies should look for other literature. Makes me wonder, which modern C book based on the latest ISO C standard would you recommend?
I can recommend godbolt compiler explorer (it's an online tool) if you want to compare disassembly between different compilers or if you're just on the go
@@ralfbaechle Noted on the -g, I will try without. I am not sure if the book question is for me, but personally I would not recommend any book for a new skill. TH-cam tutorials are a much better bet to learn a new skill I think. Having learnt C I used books to learn sockets, pthreads and POSIX, but I would not learn something new starting with the written word unless I already had some vague competence in an area that was related. I learnt C on an AT&T 3B2 computer sitting at a Wyse-50 terminal (that dates it), I found the K&R book next to useless at the time, it has too much assumed knowledge.
An array isn't just an address because it knows its size. It's literally an array type. It decays to a pointer at the slightest provocation, especially in C, but it remains a distinct type. In C++ you can pass array types by reference and/or deduce them as template arguments, thereby keeping their size around and avoiding their decay.
That's not quite correct. The type and size information you enter for an array is not stored as data. The compiler uses that information to generate machine instructions when initializing the data stored in the array. The array name you entered, however, becomes actual data - an address. This is subtle stuff and you'd probably need to look at the disassembly to understand this fully. I think this is a common area of confusion and I'll make another video to explain in soon. Best wishes Huw
@@LearnWithHuw It is correct. As the standard says, "An _array type_ describes a contiguously allocated nonempty set of objects with a particular member object type, called the _element type._ The element type shall be complete whenever the array type is specified. *Array types are characterized by their element type and by the number of elements* in the array. " (italic emphases in the original, bold face emphases mine). You can verify it yourself by using the sizeof operator. Like I said, all this stuff is much clearer in C++ because it has more powerful introspection capabilities, but there is no question that, even in C, an array is more than a mere typeless address.
Yes, you are totally correct here. In regards to @LearnWithHuw comment, while compilers don't generally store the size of the array in memory in the generated machine code, they can if they want. The memory layout like this is not prohibited by the language. But the compiler absolutely tracks the array size as it compiles as that array size can be referenced via sizeof or in C++ templates.
If we are talking about semantics then an array is not an address - it is STORED as an address in program's executable, but an array is (surprise) an array with all it's contents, not just an address. And a pointer is just a variable containing memory address. So, if we are picking on things like this, lets be specific. You might ask "what's the difference?" Well, a value stored in a pointer can even make zero (nomen omen) sense, while array's address and contents is known at compile time and it's always there.
In C an array literally is an address. The "idea" of an array from the programmer's perspective may include the data stored at offsets from that address. But the array name you entered becomes quite literally an address. Best wishes Huw
@@LearnWithHuw well, if you say that an array is literally is an address then why don't you say that bool, int and short variables are addresses as well and make case out of it? They all are treated similarly by the compiler, so it's not fair if you call them differently for some reason. So: can we agree that all variables are stored as addresses, but they are abstract entities and should be treated as such? And as a side note: if you ask me, the whole confusion comes from how index operator works and how similar is to use it for pointers and for array variables. And it is a valid comparison - they are similar in use, even if it's just some syntactic sugar.
@@viigihabe we can all pull that "abstraction border" back and forth to tell some "shocking" things, but the logic is merciless - if you not treat a variable as an address you shouldn't treat an array as such. And you will never compile a program like in your example with a modern compiler.
I don't understand all the confusion in the comments. I would think an array is just a sequence of data. So the program really just needs to know the start of that sequence of data (an address) once the compiler is done with it I guess. It's just a sequence of data stored in memory with a start address to reference said data?
I used to read the C++ standard, while based on C I realize it isn't the same. Also it's been a while since I looked at it.... but I don't think you are totally right here. So point 1, an array is not a pointer... this is totally correct. But point 2, an array is not a variable. I don't think that is right. What you are seeing is the behavior when an array gets converted into a pointer and when it does not. This is why people confuse the two, because arrays convert into pointers in many scenarios. Also the lvalue and rvalue rules are strange and confusing but I don't think an expression not being a valid lvalue means it isn't a variable. I can not do (i + 0) = 0. This does not mean "i" isn't a variable.
im a nurse teaching myself code who has no prior experience with coding and am a month in and this was the best way I have heard this explained thank you so much for this!
This is fantastic. I struggled with this in my early days of learning C, since I couldn't build a good mental model around what I was seeing. Eventually I built a good enough mental model that I don't generally have issues with pointer decay, but this clarified a lot of things. Good stuff to think about!
The mental model is that an array is really literally an array in memory. Just when you pass its value it becomes a pointer to the first element. Huw's view that it is an address is wrong, as the sizeof() of an array is not the size of an address.
@@ronald3836 sizeof is a compiletime function that stores the known values of your array initialization. An array in C, is, however, an address. This is a fundamental feature of C. As quite a few people are having so many problems understanding this, I will upload another video soon to explain this in more detail. Best wishes Huw
@@LearnWithHuw no, an array in C is the memory area with an array structure imposed on it. That using an array in an expression converts it into a pointer value to the first element (i.e. an address) is an important feature of the C language, but that does not make an array an address.
Another way to explain it is to go into detail about how the initilizer expressions are implemented. With void foo () { char str1[] = "Hello"; char *st2 = "Goodbye"; } The compiler will create a string constant in the program image containing the data contents of the string "Hello", and another one to hold the contents of the string "Goodbye". Then, since these are auto variables, for st1, the function prolog (i.e. code added by the compiler whenever you enter a function before the user code is executed) will allocate space on the call stack to hold the array and COPY the characters from the initiaization code into the allocated stack memory. For st2, the function prolog will allocate space on the stack to hold a pointer value and initialize it with the address of the existing "Goodbye" string. Whenever you use str1 or str2 in an expression, the compiler will look up the symbol table entry to see what processing needs to be done and adjust its behavior accordingly.
You might even say that _every_ identifier / named variable represents some address in memory (stack and/or heap), and pointers (specifically the & ) simply expose that address for use in code.
No. Each variable has indeed an address, except for registe variables. But the content of both a pointer variable and an array variable is (another) address.
@@maxaafbackname5562 The content/value of a pointer variable is indeed an address (= pointer). But the content/value of an array variable is the list of element values of the array. (Of course arrays cannot be passed by value, but they still have a value.) When you initialize an array, you assign it a value. E.g. int a[5] = { 1, 2, 3, 4, 5}; assigns the value { 1, 2, 3, 4, 5} (of type int[5]) to a.
It's why you don't use fixed arrays, but use pointers and allocate memory blocks for your arrays. and then you use pointers to pointers, voidpointers, functionpointers, and self modifying code :D
The real source of confusion is when you pass arrays to functions. Because then the conversion from array to pointer to a type is done automatically. So many people have the idea that an array is the same as a pointer, or that the compiler treats them as the same thing.
Plus it gets confusing when you declare it with array syntax in the function signature, but still isn't an array. But it is an array to C++ template typing.
I agree that an array is not a pointer, but it is not an address either. From the C standard (C99 and forward, the definition in C89 is more ambiguous) we get: "An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. The element type shall be complete whenever the array type is specified. Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T, the array type is sometimes called ‘‘array of T’’. The construction of an array type from an element type is called ‘‘array type derivation’’." As can be seen there is nothing in the official definition that say that it is an address, it is a contiguous allocated nonempty set of objects with a particular member object type with a known length. How compilers implement them can be by keeping internal pointers to it, or by literally copying the memory address to the first element, but neither the pointer nor the memory address is the array. It typically has and address, but it is not the address, that is like saying that a house is the street address it is located at.
The confusion arises from the fact that "array" is used both to describe the high-level idea of a sequence of data items and the lower-level description of the array identifier/definition. When compiled and run, the array name literally is the address of the first item of the array (the sequence of data items). Use your debugger and prove it to your own satisfaction. This is clearly something that is confusing many people and I will do another video soon digging down even deeper (using a disassembly) to show this in even more detail. Best wishes Huw
Think of it this way. The street address is the location where the house is built. House #1 is the first item in the street or in the array. The array address is the location of a piece of data (the first house in the street/array). But a pointer is like an envelope on which the address has been written. The pointer itself lives at another address. The array doesn't.
@@LearnWithHuw that is beside the point I tried to make. I want first to apologize if I came of as a bit grumpy, I woke up with a headache. What I tried to convey is that the array is not the same as the address to the array, it is the contiguous sequence of a single type, in your case a chars. You can create arrays in C that would not have an address at all, for example an array of 4 floats can in some cases be optimized by modern compilers to be a single vector register (such as xmm0) in the CPU. Since CPU register do not have a memory address such an array would not have an address. The best way to show that is to set it up in Godbolt compiler explorer (I tried to do that but I think YT don't like links in comments). For example the function below would be optimized this way with gcc 14 and -O3 float foo(float a) { float array[4] = {0}; for (int i = 0; i < 4; i++){ array[i]+=a*i; } return array[0]+array[1]+array[2]+array[3]; }
@@tordjarv3802 I understand that this is a cause of confusion and I promise I will return to this subject soon. This entire comment thread has raised several points that need to be explored more deeply. Thanks for the comments. Huw
The local variables are created on the stack so are sized as declared so you can’t change that hence why the array can’t be reassigned, although the contents of course can be. The pointer variable is reassignable (as long as to a pointer of same size) as the contents are elsewhere in memory. Getting out windbg can be really illuminating
Actually, you got it wrong too. An array is not an address, which you can verify for yourself by calling sizeof. If you do this you will see that sizeof(str1) < sizeof(str2), assuming 64-bit system sizeof(str1) is 6 and sizeof(str2) is 8. This means that str1 is not an address, because it it were, it would have the same size as str2. However, in most expressions the name of an array decays to a pointer. But there are exceptions to this, such as when you call sizeof, typeof, alignof. What happens in your code is the call to printf forces the name of the array to decay to a pointer.
A great many people are making the mistake of assuming that sizeof is a runtime operator. It isn't. It's a compiletime operator and it simply stores the value of the array size as it was initialized. I will make a video in a few days to explain this. Thanks for the comment Huw
Sizeof(int) is not 1 neither. Which leaves the question: what is an address then? Address of a char as sizeof(char) is the only one that is required to return 1? Always, even if it is not true.
@@viigihabe sizeof is a compiletime operator. It doesn't provide any more information than is already known at compiletime. I'll have more to say on this in another video.
@@sturlamolden It was too late in the evening yesterday, so I came off with really bad presentation. Also I think I was in a middle of understanding what You tried to say and what @LearnWithHuw tried to say. I think that in Your perspective address is a pointer. 8 bytes in 64-bit systems and 4 bytes in 32-bit systems. In video he never stated clearly what he means by "address". My really bad representation of sizeof(char) etc was trying to show that address of an _object_ "char" is 1 byte (even if it is not) and address of an _object_ "int", although system dependent, but different than former. Pointer to those addresses is always the same thing: 8 or 4 bytes or even less in tiny systems. Lack of clear statement "address of an object" is the source of confusion here. By the way I was playing today a little with function arguments as arrays and I didn't know that before, but array sizes can be preserved. The usual f(char[5]) decays to f(char*) but f(char(*)[5]) doesn't. So, array as a type definitely exists. But then you have to pass an address of an array not just the symbol that presents it in code (variable). Address of an array that is. Btw, if you pass address of wrong-sized array the C-compiler will only produce warning (gcc at least does so), but c++ compiler (g++) will yell at you with full error.
One implication to keep in mind is that when an array is initialized with a string literal, a memory copying takes place. With the pointer variable, no such copying is performed. Opening up the Disassembly window will show that. For a short string like in the video, there will likely be a few register assignments, but on a larger string some form of memcpy() will be called. Something to keep in mind while initializing stack arrays this way - the memory copying will be performed on each call, which can hurt performance and use up stack memory (do it in a recursive call and you may run out of stack space).
I randomly came across your video and I have been using c/c++ to build network software for a few years now. I have nothing to add to the conversation as it was laid out in the video well, but I wanted to say I wish I were born a few generations before. I don't like the way we current write software and how much just horrible software is out there that is covered up by hardware power. The older generation of engineers were much brighter and I wish I had the opportunity to work and learn from them in person.
Thank you. Yes, there certainly is a generational difference in approach to software development. Many of us who are a bit older had to find out how programs worked the hard way (trial, error and lots of debugging). And, as you say, we couldn't afford to waste valuable resources like memory or disk space. Every byte counted. Still, you are clearly younger than I am and you seem to have the same feeling about the art of software development - so there's still hope! 🙂 Best wishes Huw
@@LearnWithHuw Thanks for the response! Yes I am in my late 20's. I hope I can learn and carry the deeper knowledge into the next generation of developers!
I suspect this is indeed an implementation choice. So it could fail for compilers that implement bound checking and store the size of the array in the first bytes allocated for the array object. If someone knows better, then please correct me.
@@ronald3836 the sizeof a struct can be larger than the sum of the sizeof all of its elements (and often is due to memory alignment restrictions). Conceptually the same _could_ have applied to arrays... except unfortunately Denis Ritchie, instead of coming up with a separate compiler operator to find the number of elements in an initialised array (For example something like 'dim(a)' being 3 for 'int a[]={11,22,33};') he decided to specify that 'sizeof(a)/sizeof(int)' gave the dimension. That arbitrary choice (which I'm sure made sense at the time) has locked us in to never allowing extra information to be stored in array (decide with haste, repent at leisure). So, in practice 'A' and '&A' will be the same address... although it needn't have been that way!
I'm a seasoned C programmer, even though it's a long time since I last used C. I have to admit, that I only halfway got it right. I got it wrong in that I thought of an array reference as a pointer. I got it right insofar as I would never mutate an array reference or expect such as reference to be a variable. I developed a personal style during the later years of my C programming routine, which is a direct result of long debugging sessions. The parts related to this issue (arrays and pointers) are: - always declare everything constant when it's not known to be mutated (lots of `const * const`). - only use array notation when an array is created (stack allocation, value semantics) - never mutate function arguments (unless necessary and prominently documented and (function-)named) This goes to show that even if you spend a lot of time thinking about how to get something right, you still often have to pay a price. Here the price is to have to admit not knowing something as basic as this. Embarrassing.
It absolutely is an address. I've just uploaded another video that digs down deeply to show exactly how this works (in memory and assembly) which you may find helpful: th-cam.com/video/bFAO99USrYI/w-d-xo.htmlsi=VMQKqSdyn1psWT0k Best wishes Huw
What is true is that an array rvalue may be converted to a pointer to its contained type, without an explicit cast. That, in turn, is where the misconception arises. Using that as a shortcut to pass an array by reference into a function is pretty much necessary in C - there are no array parameters, only functions. Because that doesn't allow for conformance checking on arrays, that's a shortcoming of the language. Dennis once told me that he considered that the "single biggest botch" of C's design.
I think the confusion arrises with the behavior of decaying arrays, where all of a sudden it 'seems' like the array is a pointer because it behaves as a pointer. #include void doSomething(char* myCharPtr) { printf("%s", myCharPtr); } int main() { char str1[] = "Hello"; doSomething(str1); return 0; }
The difference becomes even more clear when looking at two-dimensional arrays (as opposed to an array of pointers, or a pointer to a pointer, or a pointer to an array)
Yes, I was thinking earlier today that showing pointer arithmetic with a multidimensional array would make an interesting video that would probably be quite surprising to some people! I may do that soon. 🙂
Thanks! Would be nice to have a video on arrays as parameters to functions and what happens there and the different ways that parameter can be specified: pointer to array element type, specified array length , unspecified array length.
"Array reference" is the same as "function reference". All function calls actually operate on function addresses and function names implicitly converted to function pointers of their defined type. Implicit conversions are difficult to figure out, and all those "in other words" answers in books and on stackoverflow create even more confusion.
For the more advanced, the real confusion comes with array arguments for functions. Do you think you get the size of the array with a sizeof on an array argument? Nope. You get the size of a pointer. Fun stuff! 👍
Agreed. I'm still unsure whether functions should be declared with (array [], size) or (*array, size). I would prefer the first, but gave up on understanding the errors I would get. Thankfully, this video has rekindled my interest in learning the difference.
@@m3mb3rsh1p I was more on the side of using pointers as arguments, but my take has changed a bit as of late. One case where I'll use an array as argument is when said array is guaranteed to have a fixed size (a fixed number of items). Reason is that it self-documents code in a better way, and additionally modern compiler can give better results in terms of static analysis. If you want to use arrays with an associated size parameter, from C99 on, the prefered way would be something like the following: (size_t n, xxx array[n]). This is allowed, and is also better self-documenting IMO, while possibly again giving more opportunities for static analysis.
@@m3mb3rsh1p I believe that if you specify an argument as address ("array[]"), you would not be able to edit it, which makes sence in the scope of this video: you have an address, a number, and you can't change a literall. To be sure you must specify -std=, -Wall, -Wextra and -pedantic. I think compilers are good enough to detect that pointer ("*array"), if you pass it as argument instead, never changed, thus it can replace it logically as address, but that's all just C type abstractions anyway and it can be literally the same thing in the machine code anyway.
The ANSI spec for C as with other ANSI specs for programming languages uses the terms "must" or "must not" and "should" or "should not". A must condition is something that the compiler and run time need to check for and prevent/diagnose. A should condition is just advisory to the C programmer that they are outside the boundaries of the language and also for the compiler and runtime. Many of the submitted bugs that our optimizer received were totally invalid because the C Programmer had violated a should condition. Writing valid C programs over a few hundred or thousand lines is hard. The standard answer to bugs where the program violated should conditions was that the C Programmer should write C code. Theoretically the code produced in such cases by the compiler could do anything it wanted, like erase all the users disks, although we tried to produce reasonable code in the cases that the optimizer detected. C vectors and arrays are ill designed.
I think another source of confusion in these examples is the use of strings and char pointers as the examples used. This brings into the discussion of arrays vs pointers a lot of the confusion around how strings are managed in C and by C compilers. In some way it probably makes for a longer video if you used arrays of numbers as the basis, but it might make the final results easier to understand.
You may find my other video here, to be useful: th-cam.com/video/bFAO99USrYI/w-d-xo.htmlsi=ZiCXicV37MYKTJrd That looks not only at arrays of ints but also at the assembly code to show what is happening at a lower level. Best wishes Huw
There is still some behaviour that i have yet to understand, specifically when it comes to passing in arrays as pointers into functions, or using arrays as function arguments. I don't remember the specifics, but i do remember struggling to pass in a char[8][10] as a char **, and i think i may have had to change the argument of the function to a char *arg[10]. But then you can pass pointers into functions with arg[] just fine, and you can pass char[] into functions that take char * with no problems as well. So I dont exactly know whats going on here.
Passing an array into a function that takes a pointer is called array to pointer decay. As explained in the video, an array is not a pointer so what you have is actually a variable with the address of the first array element (and you have lost size information, eg sizeof will no longer give the array size). But as far as you are concerned you can still treat it like an array because you can index it, and get what you expect. When it comes to multidimensional arrays, there is still only one level of decay. If you think about it, double indexing a char** does not work the same as double indexing a char[8][10], so it would not make sense for C to do this. Instead it will decay into a *char[8], which gives you the expected behaviour when indexing it - if you do not understand why I suggest looking into how the indexing operation (square brackets) works.
@@Coffe789That makes sense if we imagine that a char[8][10] is decomposed into a single char[80], but why could the char[8][10] not simply be "8 addresses, each of which have an array of 10 chars at the address". This is how I presume the *argv[] argument in the main function works, except that instead of 8 address to 10-char strings, there is an unknown number of addresses to strings of unknown length. How is that even possible?
@@Ryan-in3ot yeah so this is another scenario where the difference between an array and pointer matters. An array is contiguous memory, and if you have an array of arrays, you have a contiguous set of contiguous memory. So yes, in a sense you could think about it as a single char[80] that is indexed differently. You could absolutely have an array of pointers, that lead to other arrays, but that is not a char[8][10]. With argv, you are correct that it is an array of pointers; the characters are somewhere else in memory. The length of the array, and the length of the strings are not specified by the type definition, but you can work them out because you are given argc, and you know strings are null terminated. It is generally a good thing to let your array decay, and pass the length in separately, as it lets your function handle arbitrary length arrays.
@@Ryan-in3ot Because a 3x3 array of char is stored consecutively (i.e. 9 chars) for reasons of efficiency. For example when you have (char*)[3], you need 2 memory accesses to get the second char in the second row. This could well result in 2 page faults and 2 cache flushes. Whereas with char[3][3] the compiler knows that index [1][1] is just 4 characters from the start, hence the value can be fetched with one memory access. The length of the argv array is given in argc, so there is nothing magic about it. You get pointers to the first character of strings, and they are null terminated, as usual. Note that because it could be a varying number of arguments with varying lengths, it is not possible for the compiler to know the offset from the beginning when you give it a double index.
@@Ryan-in3ot char[8][10] is the same as (char[8])[10]. It is an array of length 8, where each element is an array of 10 chars. It happens to be stored in 80 consecutive bytes, but the right way to look at it is as an array of length 8 with element being an array of 10 chars. To declare an array A of 8 addresses/pointers, each address pointing to an array of 10 chars, you need to write char (*A[8])[10];
Yes the compiler simply generates code that inherently contains the address of the array (regardless of whether it's on stack or heap), such that when you reference &array (or array[]), then that's the address you get? The same thing applies also with structures? (It's been a few decades..)
No, you can pass structures by value. You can also assign the value of a struct variable to another variable of the same type. So if you insist on passing an array by value in C, I guess you could wrap it in a struct.
@@corwin-7365 True, and it surprised me when I found out it was possible to pass structs by value and to return structs. It is possible that the first C compiler I used on my Amiga did not allow it.
what version of C are you talking about ? The reality is that arrays (even immutable stack allocated one) are still pointers. That's what they are. the[ ] operator is a de-referencing + offset, that's what it is. Of course the C type system is a little bit weird when it comes to arrays, as are the memory layouts. I feel its more a weak point of c, than an "arrays are not pointers" fact.
If you think about it terms of assembly or machine code, the two will be the same. Indexing is indexing. But to the C language itself, they are distinct types. The video shows how different expressions involving arrays vs pointers will print differently. Sizeof is another obvious difference. C++ takes the difference further with templates but that's C++.
@@ronald3836 C type system is a bit twisted like that. An array is an indexable memory zone though. That's what pointer are. You can declare a variable sized array at the end of struct. What would be the sizeof() of it ? Does it means its not an array ? Nor a pointer. What is it then ?
@@automatescellulaires8543 A pointer is not an indexable memory zone. A pointer is an address (with type information). An array is not an address. Therefore an array is indeed not a pointer (but the video gives the wrong reasons).
@@ronald3836 a void pointer might not be an indexable memory. A (any non void type) pointer is though. You can dereference it. And you can ++ it. And you can [x] it.
So one could also say, that every variable is a short form of a typed 1 element array, and that a pointer is basically a one element array that can hold an address as typed value. In that way of thinking there is no difference between a pointer and any other normal variable (including pointers) that they all are just constant memory adresses to locations where a typed value can be stored. But only pointers are capable of storing adresses, and arrays are typically not except if it is an array of pointers. So, an one elemnent array of Pointers is equivalent to a pointer maybe, but an array in general is - like EVERY variable just an address of a location where a typed value can be stored.
Essentially this is true. Every variable of every type has an address at which some value is stored. An array declaration/initialization, however, allocates memory sufficient to store multiple data items. With a bit of jiggery-pokery, you could indeed use an integer as the base address for an array of sequential data items (though this is definitely not recommended 🥴)
I believe you should write "pointer variable" instead of "pointer". A pointer is an address/reference (to an object of a certain type). A pointer variable is a variable that holds such a pointer. An array is neither a pointer variable nor a pointer.
There is only one slight exception I have - an "array variable" is in my opinion actually what K&R sought to implement, that is, a vector of modifiable values. in this sense, an "array" is a variable like any other base type variable. You can't change the address of an "int" either, for instance. I think the key is to make very careful distinction between "array" and "array name". When speaking of an "array", some amount of variable-content memory is part of its essence. Wonderfully cogent and stimulating video, thank you!!!
Just thinking out loud here, but would it not have been better simply to disallow the use of an array name without indices? p = &arr[0] is almost as easy to write as p = arr, and would make the whole thing consistent. You could then call an array a variable with no equivocation.
Thanks. I think people do often confuse the data stored at offsets from the address with the array name/address itself. So when people talk about "an array" they are often thinking of the data stored at offsets. This is probably more so now than it used to be as so many people come to C after learning higher-level languages such as Java or C#. Best wishes Huw
@@LearnWithHuw Of this there is no doubt. Same goes for those coming to it from FORTRAN and BASIC. I do think the way C does it is a kludge, evidenced by fact that "arr" and "&arr" are equal.
@@karlkessler6017 Well, consider a statement like "int arr[5]": `arr` and `&arr` hold the same memory address but have different types. The former is of type `int *` whereas `&arr` is a pointer to the whole array, not just an element. The whole array has the type of `int[5]`. Therefore, in our case, the pointer has the type of `int(*)[5]` or pointer to an array of 5 integers. Consider the following difference in pointer arithmetics (considering a 4 byte integer): `arr` is a pointer to `int`, `arr++` will translate to `arr + sizeof(int) = arr + 4`. But, `&arr` has the type of pointer to an array of 5 integers. `(&arr)++` will translate to `arr + sizeof(int[5]) = arr + 4 * 5 = arr + 20`.
Very well explained. 40+ years ago, learning from the 1st edition, I remember taking a bit of time to get this right and it's been clear ever since. Yet, as you said, many programmers find this concept very confusiing. Compiling various pointer and array definitions and references followed by subsequently examining the generated assembler can be very helpful in driving the differences home.
But looking only at the generated assembler hides the type information from you. Without understanding the types of the various pointers, you have no good way of understanding what a change to your code will do.
array = group of variables (in the sense of variable contents) of same type, starting at a fixed address; name of the first variable in the array is var[0]
@@maxaafbackname5562 I typically do but I know from log experience that students who need to try to count a certain number of bytes from address1 to address2 often find the process much simpler when in decimal than hex.
Why not use %p, or at least %u, for printing addresses? Another important difference between pointers and arrays is the behavior under the sizeof operator. Sizeof(pointer) is the size of the pointer variable, usually 8 in 64bit machines, whereas sizeof(array) is the size of the allocated memory for the array.
"In C++ an array name is a constant pointer to the first element of the array." - SAM's Teach Yourself C++ in 21 Days; 1997, p293, Second Edition, written by Jesse Liberty.
Many C++ books have elements of the language wrong. I remember reading through ACCU book reviews where some books where criticized quite severely for being wrong in far more significant things.
My understanding, is that an array is it's own type, has it's own size and so on. However, arrays get converted into pointers very quickly and easily. In the video, the array is being passed to printf. Function calls are one common scenario where arrays are converted to pointers. It is a pointer by the time printf has it.
Well, books contain errors. C++ arrays are not pointers, but in rvalue contexts, array names are replaced by constant pointers to the array's first element (which BTW is not the same as a pointer to the array, although it has the same address).
@@__christopher__ That is true. A pointer to the array and a pointer to the first element have different types, but alias the same address. By aliasing rules the compiler is allowed to assume they are not aliased, and optimise according. This can cause havoc, unless the programmer is careful.
This is all rather silly. If an array "*is* an address", then sizeof on an array should always give the size of a pointer, which it only does if you try it on a function parameter that is declared as an array, for entirely separate reasons (ironically the practical reasons that give rise to the "special type of pointer" idea). Even more ironically, it is those reasons (that an array 'decays to a pointer' when used as a function argument) that entirely explains the behaviour of printf in your examples. As for the statement "an array is not a variable", what is that even supposed to mean? Arrays can certainly be mutable. While you can't use an array name as a modifiable lvalue, that's also true of struct objects (EDIT: that's actually wrong - struct objects *can* be modifiable lvalues, so in this sense arrays are not 'variable', only their elements) An array is an aggregate, a collection of objects. Everything beyond that is an implementation detail.
An array is also an address that casts to a pointer. They're both addresses. I don't think it's ironic. It all makes sense given the time and context of the C language's creation. Especially given some assembly language experience. It's all addresses.
You are confusing array as a *** name *** in C code that used to access array type object with *** data *** that stored at the address which IS the array name. You can't change it, and it was demonstrated by taking a pointer with '&' operator from the array name, storing it as integer for printing and storing it as integer directly by name. It is the same, because array name is not an lvalue; it has no space allocated for it because it IS the space, like 5 is just a number 5 in C source file. Pointer on the other hand is an lvalue, and you can get a pointer with address where that pointer is stored by '&' operator. This is the same as function names: you can't edit them after you declared a function name because this IS the code. Their names are implicitly converted to pointer to functions when you saving them into lvalues and when calling them (since function call operator accepts function pointers, not the names).
@@rogo7330 A pointer is an address. I think the video confuses pointer variable with pointer. An array is clearly not an address but an area of memory (with an array structure).
"I love abstractions. I like the whooshing sound they make as they fly by.” ;-) An array, is a pointer, but at a [completely?] different abstraction level ... ! Not that the virtual memory it points to is any realer ... I makes you think, doesn't it?
Interesting. I once encountered an explanation of the code you've shown, where they said that the `char[]` is allocated on read only memory while the `char*` is allocated on read/write part of memory. It's interesting that in your demo, it's clearly visible that str2 is allocated after the whole memory allocated for the "Hello" characters. Maybe this prevention of the user to rewrite the address of such Heap allocated array is part of some memory management that happens when we get out of the scope of the function?
In fact, a string assigned to an array (char[]) is in read/write memory. Look at this: char z[] = "hello"; Here char z[] is in read/write memory. So I want to change a character, like this: z[0] = 'z'; The result would be "zello". So this shows z[] is indeed in read/write memory but the string "hello" is in read-only memory. That's because "hello" is a string literal. It turns out that when you run the code shown above, the string literal "hello" is copied from read-only memory into read/write memory *at the address* of the array z. That address cannot be changed. However, the characters at that address and adjacent addresses *can* be changed. I think we are getting into deep water here and this really needs another video to explain fully. I'll try to do one soon. As for stack corruption. Don't assume C will protect you from this. You may want to my other videos on this subject that explain this in more detail. This one is probably the most relevant: th-cam.com/video/X7uj_uGTOfk/w-d-xo.htmlsi=DXkms19vz2r9fHEX Best wishes Huw
String on the right of the array initialization is just a special case of initializer list. It is like creating a struct or an array of ints and just putting numbers one after another separated by coma. String literal also adds null byte at the end. So basically char a[] = "Hello"; is the same as char a[] = {'H', 'e', 'l', 'l', 'o', 0};
This is entirely compiler and system dependent. Some systems can store constant data in readonly memory or in write-protected pages. Not all can or do.
@@sturlamolden To illustrate your point, the following code generates a warning (and I guess potentially a crash when str[0] is being written to later): char *str1 = "hello"; But char str2[] = "hello"; compiles fine because the string litteral is "copied" into the array of char str2 (or rather, the array is initialised).
First. You stress out that array is only address. It's not true. At least it has type. Second. It's notion / definition "quagmire". I don't know how in C specification called typed names. Those like variable, constants, array etc. But all of them have type, name and other properties like can it be changed and so on. I have urge to call them variables even if they are constant. My bad. From the perspective that it points to first element -- it's true. That's why someone metaphorically say it's just pointer. But you can't even say that it's constant pointer, because constant pointer has different behavior when you get reference to it. Also there are more weird things with multidimensional arrays like int arr[][5] or char **argv vs char *argv[].
Hm. First of all, thanks for pointing out the confusing terminology within the (academic) programming language community. :) As you suggested, in academia one tries to be mathematically correct/meaningful. Of course there are still oversights especially in first editions or mix-ups in later editions when fixing only certain points. However, in my humble opinion, such forced distinctions are counterproductive and add to the confusion if the underlying concepts are not well understood. For example, I can declare a constant pointer in C. Depending on the edition, this would be a constant variable, which makes little mathematical sense. Technically, both are locations in memory, and from the programmer's point of view, it still makes sense to call them (and arrays for that matter) variables, while ignoring compiler optimizations etc. That being said, I do, of course, agree with you that arrays are different from pointers in that they give you the address of the first element of the array without indirection. ;)
I am planning another video to dig down deeper into all this to look at exactly how arrays are compiled and initialized. Thanks for the comment. Best wishes Huw
It is confusing, and not quite correct, to say that an array is an address. First, to be very, very clear, an address is a value that specifies a location in memory allowing a read from or write to that location. A pointer is an identifier which resolves to an address; the type of the pointer signifies what meaning to assign to the value at that address, in particular the size. In C and C++, a char pointer (char *) signifies the beginning of an array of char (a char** signifies an address containing a value that can resolve to the beginning of an arrayy of char) and is quite often functionally interchangeable with notation [] signifying a string constant. IIRC, you can indeed overwrite the storage of a string constant using a correct interface to do so, even in C. In any case, you certainly can declare, and use, a char[] of some size to use as a buffer, for both reads and writes. But in using a char* to address an array of char (aliasing), usually the semantics degrade to pointer-to-char. Notions of size go away, and the only boundary semantics in the language relate to the NULL char value. Hence many difficulties with buffer overruns. You have to be very disciplined in the use of pointers. You always need to ensure the address value in the pointer is correct and, particularly, in bounds of any array in which it is operating; this is true for other types of array than char[], like int[]. C++ offers the possibility of consolidating buffers of some size with operations guaranteed (if you program them correctly) to abstract and enforce bounds, all within a class. Any time you pass a pointer value as a function argument, you can pretty much count on the loss of any associated array size, so a size argument is usually also required for element counts greater than 1. Nearly all of the above babble may be best understood by looking at code well written to handle these things correctly, and sometimes with the help of drawing out the memory cells (paper, whiteboard) comprising the array being designed and managed. There is a reason that people writing code are paid better than people flipping burgers.
After watching this video, i actually came up with an adendum which could help solidify this concept, using some reverse logic: Imagine that the array *was* just a pointer. This would mean that the compiler would in fact, be pushing an additional value, the size of pointer, onto the stack in addition to the array itself, which would be undeniably redundant. I feel the reason why most people believe them to be the same, is because typically one has no need to "pass the array around" within the scope that it gets declared. Its only when it gets passed to another scope, that it *decays* to a pointer because you cant simply pass an array, since it doesnt inherently know how long it is after it is created, and has no choice but to pass a pointer. But whilst in scope, the space that has been pushed onto the stack for that array cannot change for the duration of the function call. Excellent video overall, very enlightening!
An array certainly does inherently know how long it is after it is created. Still, C does not allow you to pass around whole arrays. I suppose this was done for efficiency reasons (and to promote more generic code: no separate functions for different sizes of arrays, as Pascal requires). It would be possible to create a dialect of C in which you can pass around whole arrays (it wouldn't be C anymore, but you wouldn't have to rewrite that much of the language definition).
@@bundlesofun9568 The compiler keeps track of it, as you are well aware. Just like the compiler keep tracks of the layout of a struct and of the signedness of integer types, etc. Are you saying C does not have signed and unsigned types just because the signedness or unsignedness is not stored in memory? Are you serious?
@@bundlesofun9568 in C, you cannot assign a pointer to int[5] to a pointer to int[6] because they are different types. int a[5]; int (*b)[5]; int (*c)[6]; b = &a; // works c = &a; // compile error So the array "a" still carries the information about its size. Not if you use the name "a" in an expression, because it will decay/be converted into &a[0]. But it is there if you write &a to obtain a reference to the array, i.e. a pointer to the array object. And we are talking C here, not assembler or machine code.
I still don't understand why is it impossible to place different values to array being sure stay in boundaries. Well, I realize it is just a C rule. So the main problem is in the way array is initialized. It uses the same symbol as valuable assignment does. Using '=' thing for both operations confuses inexperienced programmers. Declaring array is not just a putting value to memory, but complex activities with addresses and so on.
Thought a bit and figured ou that there's not just '=' in array declaration that works, but '[]=' ! Brackets belong to operation, not array name. They ARE operation.
i think they want to emphasize array is a block of memory contains array of some data structure cells like struct don't treat pointers to array as if they were pointers to primatives, take care of this memory block internal structure
Every single thing in memory is a pointer. If you have programmed bare silicon in asm this is obvious. Once you understand the concept its perfectly logical and easy to understand. If not you are not a c programmer.
What is missing in this video is constant pointer. It would be interesting to see how it is treated by the compiler. And probably you should add that dynamically allocated array with new is still a pointer.
When I think about arrays and pointers being the same I think of where I can pass it to. My C/C++ is rusty, but from what I remember if you have a parameter that's an int*, you could pass an int* variable to it or pass an array. Or the array maybe needed to do something like &arr[0] (couldn't remember if you need to do that or if that was just something from example code playing with this years ago). I'd never thought they were the same before you'd pass it in to something's parameter. The other important similarity is you can iterate over the array elements with pointer math. int *pInt = arr; if (*(pInt+1)==arr[1]) { printf("the same"); } else { printf("different"); } If the array is filled with unique values that blurb should print "the same" and you can trust it wasn't dupe values happening to line up.
If you pass "arr" as a function argument, then that is equivalent to passing "&arr[0]". If you really want to pass a pointer to the array object that is named "arr", then you should pass "&arr". The value of this pointer is the same, but now it is a point to the array object instead of to the first element only. You are correct that an array object is not the same as its address.
@@ronald3836 For a datatype[x], I thought arrays in C were just a contiguous chunk of memory that could hold x datatype items end to end. So there isn't really an array object with extra state beyond its items. Even if there's item padding (#pragma pack or something??) I thought the padding was always after the element data, and I thought also that padding was counted in sizeof (a lot less sure about that).
@@MikeIsCannonFodder Indeed there is no further state, but the proper mental model is that the array is that chunk of memory and its structure (number of elements and type of element). A peculiarity of arrays in C is that the name of an array is in nearly all cases converted into a pointer to the first element of the array (but not when & or sizeof are applied to it).
@@ronald3836 For your last sentence are you talking about dynamic arrays from malloc? I've only been talking about static arrays on the stack. If you are talking about dynamic arrays, then that makes sense since you'd be getting a pointer to a pointer.
@@MikeIsCannonFodder I am also talking about statically allocated arrays. In C, if you declare int array[10]; then using "array" in an expression is in most cases equivalent to writing "&array[0]". For example, "array + 1" is the same as "&array[0] + 1" (and is the same as &array[1]). This is not the case if you write "sizeof array - sizeof array = 40 (if sizeof int = 4) - sizeof &array[0] = 8 (on a 64-bit system). This is also not the case if you write "&array": - &array is a pointer to the array object: it will normally point to the same address as &array[0], but it does not point to an int object (of 4 bytes) but to an array object (of 40 bytes). - &&array[0] will not compile.
I only have one correction: A constant pointer is also not assignable after declaration, and thus it is not a variable. So your dichotomy "pointers can be assigned and are variables" vs "array is not that" is also confusing.
This is good to know but for me obe(overcome by events) as while working in more modern c++ unless absolutely necessary I am not allowed to use c style arrays and pointers.
Multi-dimensional arrays (such as int[5][6]) will always require exactly one indirection. Ragged “arrays” (such as int **) will always require more than one indirection.
C does not have a concept of multidimensional arrays. What you have there is an array of arrays. As for ”ragged” arrays they only require one indirection as well, at least in mot algorithms where we iterate along the data. Proper C code will either use a pointer as cursor or copy an element from the pointer array into a register. Then this pointer is indexed like an array, but kept in a register for multiple indexing. Both methods tend to yield single indirection, even for ragged arrays. If you se double indirection in numerical C code, it is a clear sign the programmer was incompetent and should have used Fortran.
@sturlamolden C definitely has multidimensional arrays. An example is int rotationMatrix[3][3]. All nine values are clustered together in one contiguous memory area, accessing any element requires exactly one indirection. Ragged arrays (like main's char *argv[]), is different. To access a specific character in an argument, the CPU has to do two indirections, one to get the address of the target argument and another to get a specific character in that string. (ex: char firstLetterOfFirstArg = argv[1][0]). rotationMatrix[0][0] will cost one indirection, while argv[1][0] will cost two.
@@breathermachine2 Only an amateur would write argv[1][0], argv[1][1], etc. and incure double indirections for each letter. You start with const char *arg = argv[1]; This is an indirection, but you only do it once. Now you get all the chars in the first argument with a single indirection, from the pointer arg. Then, repeat this. You do not get double indirections for each char. If there e.g. is 10 chars in the first argument you get 1.1 indirections per char, not 2. In C code though it is more common to see the coding pattern const char *arg = *argv++ instead of indexing when stepping through a ragged array. And then you iterate over the chars using a while(c = *arg++) loop. Then repeat arg = *argv++, repeat the while(c = *arg++) loop, etc. This does not produce double indirections on each char. However, if you write argv[i][j] you do, but then you are displaying amateur level knowledge of C. As for your rotatationMatrix it is an array of arrays. It is not really a 2D array. It becomes contiguous in memory because in C one declaration gives one contiguous block of memory. The row-major nature of C is actually not a convention, like the column-major ordering in Fortran, but rather a byproduct of rotatationMatrix being an array of arrays. Which is faster? A novice would say the array of arrays, because is incurs less indirection on average. However, to accelerate numerical code the use of SIMD instructions require special data alignment. Intel’s AVX instruction set requires 32 byte aligment. How can we ensure this? The ragged array makes this simple. We simply overallocate each row ro fit the data into an aligned block. And while it has more indirection on average (or only alightly so), it can allow the compiler to use more advanced instruction sets. On a processor with hierarchical memory, the cost of a flop dwarfs that of an L1 cache lookup.
@@sturlamolden Call me amateur or whatever, my point still stands. I'm not sure what point you're even trying to make. intMatrix[0][0] is one indirection, argv[1][0] is two. That's it. I know all of the things you mentioned, I'm no stranger to pointers/arrays. So now you tell me, what exactly did you correct in what I said?
@@breathermachine2 It is correct that argv[1][0] isolated incurs double indirection. However, this is not true for algorithmic code on ragged arrays in general (unless the programmer is incompetent). When we have multiple data access along a row, the double indirection can be amortized to single indirection.
There is a problem in your explanation in my opinion. Now I could be wrong , because i've programmed in C quite a long ago. The Example you've shown have used String Literals as an Array Object. But Compilers Put Any String Literals initialized into an array in "Code/Text" Section of the binary (.pe / .elf ). I think you should use An array of integers with a fixed number of elements and use scanf() to put the values into them. And Then Compare Them with an Pointer to an int. I think that would be a fairer comparison.
I guess there is something to be gained by owning the second edition ofthe book. I have the first and I thank you for making this video to clear up that ambiguity.
i always get scared doing things like char *str = "Hello"; because i dont know if that is guarenteed to allocate 6 characters on the stack. Im not even sure if the stack is where these string literals go, but they seem to be modifiable in both the pointer and the array case, so i cant imagine another place they'd end up.
string literals are stored almost as global array variables. On some platforms those memory sections are read only (like instruction sections), on others it's read/write-able. Consider this example: char global_array[ ] = { 'G', 'l', 'o', 'b', 'a', 'l', '\0' }; int main( ) { char local_array[ ] = { 'L', 'o', 'c', 'a', 'l', '\0' }; const char* string_literal_pointer = "String literal"; } global_array is stored in global memory and is guaranteed to be read/write-able. local_array is stored on the stack and will be 'created' when the main function starts and will be 'destroyed' when the function ends. string_literal_pointer (remember, pointer is just an integer variable) is stored on the stack and is pointing to some global memory area, where the characters are located. I don't know for certain, but it's illegal to modify the string literal data (as far as I can tell). It's probably platform specific, but is always assumed to be read-only. Practically, it means that there is some space in the process's memory dedicated to the stack. The program would keep track, how much of stack space the program is using by having an address of the top of the stack somewhere in memory (in RAM or in CPU registers). Your compiler then would count how much memory it would need to store all local variables of each function call (plus some call related low level data) and would just increase the stack pointer by that amount on each function call, thus claiming that chunk of memory. And when the function call would end, it would decrease the stack pointer by that amount, thus releasing that chunk of memory. In that sense, a local variable is just an offset in that chunk of stack memory. Global memory, however, is always there; it's size is calculated at compile time and is given to the process when the program starts. It's never created or destroyed
The str variable itself goes on the stack. It's a pointer, so it contains an address that's likely 64 bits nowadays, so 8 bytes go on the stack. For the characters themselves, it depends. In a function, it goes on the stack, so that it will disappear when the function exits. Unless you declare it static or if it is a global variable. Then the value must exist for the lifetime of the program and the compiler will store it in a data segment. This is neither the heap nor the stack. It's a section of your executable file that gets loaded in memory at startup.
@@ErikBongers this is an optimization tho, one could pass the address of the string literal to be used somewhere else, thus it 'must' have global storage. So, you shouldn't rely on it to be allocated on the stack
When you do `char *str = "Hello";`, the string literal "Hello" (and the null terminator!) is stored within a read-only section of memory inside your program. Note that it's read-only. The proper type is therefore `const char *`, but many programmers ignore that. When you do `char str[] = "Hello";`, the array `str` exists at compile time but the string literal is copied into it, so you can modify it safely
No. A variable’s value can vary. A constant’s value is fixed. It makes absolutely no sense to say that a variable’s value is locked, because that means it is not a variable anymore.
P.S. if you’re struggling to find a general term for these things that have names in programming languages (and subsequently trying to use the moniker ‘variable’ for everything) just remember that as far as the compiler is concerned, all names are symbols. Terms we use like variable and constant (or macro or function etc) merely define what that symbol means.
The most stupid thing in explaining C is to hide from the user that there is lvalues - things that must have allocated memory for them somewhere, and non-lvalues - things that are just information, like number 5, that don't have (or don't have to have) allocated memory for them since you can't edit them anyway. Array names, function names, and something else that I totaly forgot - they don't have (or don't have to have) a variable on the stack to store their address since you can't change them, as you can't write something like "5 = 2 + 2;". I hate when someone says "decays to a pointer" instead of "C implicitly creates a pointer with this address". Wtf is "decays", how it's done, what are the effects? Conversions are well defined (until they are undefined, lol), and "decaying" is just a made up word for things that C does with addresses.
Arrays *are* lvalues, since you can apply the '&' operator to them (which can only be applied to lvalues). Ie: int a[10]; int (*p)[10]; p =&a; is perfectly valid. Arrays just happen to be lvalues that can't be assigned to. Note that in K&R structs were *also* lvalues that couldn't be assigned to... however that restriction was lifted in later versions of C.
What about constant pointers (not pointers that point to a constant)? I mean the ones thats declaration looks like some_type *const ptr; in the simpest case. How do they differ from array variables? I suspect that an array variable is more than just an immutable address to the starting address of a memory area of an array, like a constant pointer (value), since the compiler keeps its size in check. Otherwise the sizeof(a) / sizeof(a[0]) would not work to obtain the length of an array with identifier a .
They are immutable pointers so not quite the same as an array which is not a pointer at all but literally an address. That is, if you define an array called myarray, by the time the program runs, that identifier has been replaced with an address. Best wishes Huw
Arrays and pointers have the same decay type. This is why it's a lot easier using them in C++ where you have std::decay and the like to make it more explicit (and provide a more uniform calling syntax). Of course in C++ you shouldn't be using arrays or pointers either, but that's another story.
Interested in your comment about not using pointers or arrays in c++. What would you advocate in place of them? Not having a go at you. Just genuinely curious 👍
And with 'not using pointers', they probably mean: not using owning pointers, for which you'd have to manually call new/delete/alloc/free, you'd want to use a RAII wrapper that guarantees the delete/free is called in the destructor (it can't be accidentally skipped because of some error condition for example). But using non-owning pointers, i.e. a 'reference that can be null' is perfectly fine, as long as you know it can't become a 'dangling pointer', if you can't guarantee that, a shared_ptr with weak_ptr combo probably more appropriate.
@@NotMarkKnopfler For fixed-size, typed arrays, you use std::array. That has the advantage of carrying its size with it. But std::vector is better as it is move constructible but still has the same memory contiguity guarantees as std::array. As for pointers, unless you're taking the address of a stack-based object, in C++ memory should be handled with std::shared_ptr, std::unique_ptr and std::weak_ptr. These have reference-counted or RAII-style lifetime behaviour, so leaks are much more easily avoided.
It made no sense until I realized you were saying POINTER, not PUNTER. I thought PUNTER must be some insightful football term to refer to a role yet to be explained to non-footballers. I suppose it ended up well though and my Americanized ass ends up admitting that Rooney was one hell of a punter. In fact, normally in American football a punt goes high in the air trying to achieve "hang time". Rooney had none of that. Canon shots right to the proper address.
This may be clearer when I upload my next video. But in essence you need to keep in mind that the data stored at an address and offsets (contiguous addresses) can be changed but the address itself (the start of the array) cannot. I'll explore this in much more detail in my next video. Huw
This comes down to how compiler works. It treats the array as a constant, so it forbids you to change the array (you can change the individual elements however) before even program is compiled. On the other side, pointer assignment happens during runtime, outside of compiler's control. A pointer variable just points to some memory location, and you can do anything you want with it as long as your operating system/memory manager doesn't complain. In the examples above, the array str1 and pointer str2 are allocated on the stack, so if you alter str1 value using a pointer, you risk overwriting whaterver else lies after the array str1 in memory, including the value of the pointer str2.
@@LPTech-m5m You can never change the address of a variable, whether it is an array variable or a simple int variable. If a variable is allocated on the stack or the heap, it will have an immutable address. If a variable is stored in a register, then it will not have an address. This may be the case for array variables stored in AVX registers, which shows that an array certainly is not the same as an address.
@@LPTech-m5m Just pointing out why one cannot change the address of an array. It is for the same reason that no variable allows for its address to be changed. But what is the point of the video according to you? Seems to me the point is "array in C is an address" (which is a false statement).
As a C++ develo[per it is interesting to get the C only perspective as it means the same thing effectively but the language used is different without reference to const or decay. It would be interesting to continue to talk about the array of characters themselves, such as str1 is on the stack while str2 is in the text segment thus the vastly different addresses. Presumably, although bad design, writes through str1 would only have a lifetime of the stack frame / function invocation while writes through str2 would either modify global data or perhaps fault depending on the operating system / write permissions to the area where the str is stored.
Thanks for the comment. The comments in this thread have given me quite a few ideas for other videos so I'll be returning to this soon. Best wishes Huw
you are totally right. i discovered this fact by myself when writing my own C compiler. At first I made a reference pointer to the array and later on realized I didnt need it. All I needed was the actual array and its address to deal with it. True!
Thanks. It's good to have feedback from someone who's got the real world experience of this! 🙂 Much appreciated.
Best wishes
Huw
I went to school in the late 80s and compilers was a core CS requirement. You wrote a compiler in the first semester and used it for all the projects in the next one. We also made breadboard projects to demonstrate multiplication and division using only logic gates. It seems like foundational CS is no longer taught and new engineers are robbed of any depth of understanding of crucial concepts. You can't optimize code if you have no idea how either the compiler or CPU will treat it.
Had an engineer write a mail program to send to 30 mil customers. Kicks off job. Next day I ask how it went. Still running. How much is left? It sent 50k so far. Overnight? Yeah. Out of 30 million? Yeah. That will take two more years to finish: rewrite it. I want to wait until tomorrow to see if it finishes.
True story.
can you explain to mortals why it's not a pointer if you need the address?
@@user-zu1ix3yq2w once you take its value it becomes a pointer, but an array variable is really literally an "array": a fixed-size sequential list of objects in memory of a certain type. If you take the sizeof() of an array, you get the size of the full array object in memory. (From the video I am guessing that Huw would be surprised by that.)
@@ronald3836 He'd probably just say something about compiler magic.
So many seasoned C programmers need to see this. I've had other programmers fight me over this!
Oh no! Let's hope this helps restore the peace. 🙂
@@LearnWithHuw We tried… but it was no go. 😅
@@butchdean it does not help that the video is wrong. An array is not equivalent to an address. It is a memory area of a certain size with an array structure imposed on it.
As many have pointed out, an array is not an address. Claiming so just sounds like being confused about pointer decay.
As a non-native English speaker, there is a chance I misunderstood you. However I'm pretty sure you made a mistake and explained this incorrectly.
First off, I agree that the statement "An array pointer is a special type of pointer" is at least incomplete when it comes to explaining what they mean by "special". Even though an array name like str1 represents the address of the first element of the array, it can convert and becomes a pointer to the address of the first element of the array, when you use it as an expression. It is different than the Pointer the C-user creates, and probably therefore considered "special". I'm not sure.
That aside, I completely disagree with the assertion made at 0:12. You say "An array is not a variable ...".
To me a "variable" is a named storage of which you can modify the value.
You showed 2 examples. First, you showed a "char*" named "str2" that you changed from "Goodbye" to "Good Day", and that worked. So the "char*" named "str2" is definitely a variable.
Then you tried to change the value of an array named "str1" from "Hello" to "Good Day", but that didn't work. However, this doesn't mean the array named "str1" isn't a variable. It just means you used the wrong method to change its values. If you had used "str1[0] = 'B';", you would have changed the values of array "str1" from "Hello" to "Bello" and therefore the definition of what a variable is, would apply.
I believe the logical mistake you made was limiting the concept of an array to it having a name or "array name" with a fixed address that doesn't change. That part is correct but doesn't capture the entire concept of what an array is. You use that constant address to access the values stored at and around that memory location. The address itself doesn't change, but the values stored in that memory are not fixed and can be changed. Therefore, and array is definitely a variable too.
I think we agree that something about all the sentences at 8:14, 9:08 and 9:48 is either wrong or at least confusing. The first quote is like I said, if you use an array as an expression it becomes a pointer. The second quote is misleading, no matter from what aspect you want to correct it. If you want to change the word "variable" to "constant" instead of calling it a "special pointer" like before it would still be misleading. Again if you us the array as an expression it converts to a pointer pointing to the address of the first element. The third quote is correct but you have to read it very carefully to not arrive to the wrong conclusion as I think you did. The quote does not compare an "array" with a "pointer". It compares an "array name" with a "pointer". It could have compared an "array name" with a "pointer name" to say that both are constant, once defined. Or they could have compared "array values" with "pointer values" to say that both can be modified. But in this case comparing "array names" with "pointers" only shows that you have to apply a different methods to access and modify the values.
Out of context the third quote is a bit stupid. Why even mention that "array name is not a variable"? Like all variable names, an array name is a constant identifier that refers to a specific memory location you can modify. Even if you can't "pa = a" or "pa++" like you can with pointers you can "str1[0] = a" or "str1[0] = str1[0] + 1" using the indexes.
I hope there aren't too many mistakes. I rarely write this much English. ^_^
I will return to look at this in more detail. Put simply, an array compiles to address. It's not a pointer and it's not a variable. The array (i.e. the address) cannot be changed. The data stored at that address and sequential addresses can be changed.
I can see that many programmers think of an array as a fixed list of typed data elements. That's not really true. In C, the array is a constant value (an address) at which elements of a certain size (but not necessarily of a fixed type) can be stored. The array (address) is constant. The data elements are not.
That is different from arrays in higher level languages like C# or Java. In C, an array nothing more than an address. I can't explain more in this comment. But I will explain arrays in much greater depth in other videos soon.
Best wishes
Huw
The other way this plays out is how you can use sizeof on the array to get its size but in three pointer it will just return the size of the pointer.
That would be a better argument. Insisting that pointers cannot be const by definition is ridiculous.
I don't get all this debate. I've always seen arrays as a reserved memory with a defined (and immutable) number of elements that happen to have a (also immutable) starting address. The fact that you can reference an array with a pointer in certain situations is just a clever move by the creators of C.
@@mmaldonadojrIt makes sense given that an array is an address and pointers are variables containing adresses.
@@mmaldonadojr The debate exists because many people, including Huw, don't get that. Your view is correct.
If Huw adds a sizeof() to the two printf()s, I expect he would be surprised by the result.
Brilliant explanation. I am a mainframe assembler programmer since 1969 and your explanation is clearly visible in assembly.
I can see how C programmers can get this very simple concept incorrect.
Thank you for your concise explaination
I appreciate your comment. It's very hard for many programmers to understand this. I'm doing my best to try to explain.
Thanks again,
Huw
The way it was described in the K&R book is the reason so many students learning C struggle with the subtle difference between pointers and an array. Arguably, both the 1st and 2nd edition's wording is still confusing for a lot of new learners.
When thinking about things like this a bit, I tend to wonder how the world might have been different if some things while learning the C programming language were made more clear, and perhaps many problems with pointers were avoided altogether in the language design.
A few more thoughts about how to explain the difference between array of chars and pointer to a char:
- char * foo = "bar"; - Can be thought of as literally a pointer to a single "char", which the compiler treats as a null-terminated string, "bar\0"
- It stores the address of that "char" in memory, and can be assigned another address later. The stack variable therefore contains the space needed to store an address of a null-terminated string of "char". If it's declared with an initial value, "bar\0", then the compiler places that into the binary at some location, and initializes the pointer variable's value so it points at that location.
- char foo[] = "bar"; - Can be thought of as a "static"-_ish_ array of characters, allocated at compile time to the "static" string "bar\0"
- It is known at compile time, and is "static"-ish in the sense that it won't change, or perhaps we can say it's "constant"-ish in the sense that it can't change. In other words, the compiler takes the code, sees an array of characters defined and fully known at compile-time (when initialized), and creates an address on the stack to hold that string. As a result of compilation, the array's address and value are the same, and it points to this location holding the string, "bar\0".
I like the term "static" here because it is similar to how the compiler can statically compile objects and put them in the resulting binary. In the example, it's putting "bar" into the resulting binary as a "static" string. However, to avoid confusion with "static" do read the following...
**Note:** I'm trying to be careful with terms here... saying "static"_ish_ and "constant"-_ish_ because both "const" and "static" are already keywords with overloaded meanings. So, although similar, it's not exactly the same meaning as "static" C keyword, as that implies "static" storage duration and defines behavior for an uninitialized array. Meanwhile, the array example instantiation in the video, and above has the implied "auto" storage duration which does not define what the contents of an initialized array would be. In other words, an uninitialized "auto" storage duration array has indeterminate contents, which could be garbage values, or whatever emergent behavior that the combination of operating system, machine and compiler implementation create. Luckily, in the case of the examples in the video and here, we are initializing both array and pointer to something... so we can sort-of gloss over that when trying to understand just this particular aspect of the language. (Alas, even my attempt to try and explain it is probably confusing)
It's so nice to discuss pure C features as all of them can fit into one head easily, opposite to C++.
Ha! Yes, I would have to agree with that.
Best wishes
Huw
The basics are conceptually the same.
Yep! It basically boils down to,
Array: An address of an element of which the compiler sees as a single unit of sequential elements.
Pointers: A value that simply represents the address of a (potentially as a struct) grouping of data currently stored in memory.
Value -> Data
Pointer -> Represents a Value
Some people really have the hardest time apparently with wrap their head around everything being just numbers. But pointers are easy once it clicks. The CPU sees RAM as a sequence of bytes. That right there can help build an intuition of what you described.
Yes, the transition from high-level languages to low-level concepts really causes many people great difficulties.
Thanks for the comment.
Huw
@LearnWithHuw In my case, I transitioned in the opposite direction. I began writing in raw hex, no assembler, then assembler, then C, then C++. The whole thing about pointers and addreses just seems so natural having started at the bottom.
a pointer with an offset??
No, an array IS that unit of sequential elements in memory.
@@ronald3836 that’s exactly what i said
I think you’ve put your finger on the problem, but I don’t see that the explanation resolves the confusion. To say that “an array is an address” is almost as confusing and wrong as to say that “an array is a pointer”. An array is an object. Addresses are not objects; they are values. So arrays are not addresses. To say that they are is a category error. The reason people get confused is that when we write programs we often write the name of an object where a value is needed. The usual way this is resolved is to fetch the value stored in the object. If I have “int x;” then I can write x where an int value is needed, e.g. x = x + 1; On the left x is used to name the object, on the right it is also used to name an object too, but what is needed is a value, so there is an implied fetch. There is another way to convert an object to a value and that is to take its address: “&x”. Addresses are values. When we have “int a[10];” and we write “a” where a values is needed, e.g., when you pass it as an argument to printf, it evaluates to a value, namely the same value as &a[0] evaluates to. This is an implicit conversion. A good C programmer should understand when they are looking at an implicit conversion. We don’t say that an int “is a” double; we say that an int “implicitly converts to” a double. If anyone still thinks that an array is an address, ask yourself how big (in bytes) is an address. Now write a program to print the values of “sizeof(a)” where “a” is a large array.
I'm talking about C. No objects. An array is literally an address. You may use the disassembler to verify that.
Best wishes
Huw
This is the best comment on this video. I was thinking the exact same thing watching this.
C certainly has objects. (What it lacks is “class”.) Maybe take a look at section 6.3.2.1 of the ISO standard that explains when “an expression that has type ‘array of /type/’ is converted to an expression with type ‘pointer to /type/’ that points to the initial element of the array object”. Note the last two words. Also the word “converted” All the best.
@@LearnWithHuw The C standard is littered with the word object. Maybe there are no classes in C but there sure are objects. The C standard defines an object as a "region of data storage in the execution environment, the contents of which can represent values". The C standard describes an array type as follows:
> Any number of derived types can be constructed from the object and function types, as follows:
>
> - An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. The element type shall be complete whenever the array type is specified. Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T, the array type is sometimes called “array of T”. The construction of an array type from an element type is called “array type derivation”.
This is from C17, but the wording is similar or the same in both C11 and C89. As you can see a value of array type is a set of objects of the arrays element type. Elsewhere it says that:
> Except when it is the operand of the sizeof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type “array of type” is converted to an expression with type “pointer to type” that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.
I had thought until yesterday that x[i] is always equivalent to *(x+i). But this is not the case when x identifies an array with “register” as its storage class, the latter may be well defined, but the former cannot be. This shows that not only are arrays not addresses, but some arrays and their elements don’t even have addresses.
furthermore, sizeof(array_thing) returns the size of the array and sizeof(ptr_thing) returns the size of the ptr. This also trips people up.
I think a lot of the confusion comes from the fact that arrays decay into pointers when passed into functions.
Another source of confusion is the interchangeability of array index notation and ptr math on both arrays and pointers.
Also try applying sizeof to an array vs a pointer.
Great video. Will pass it along, saving me from explaining it. Thanks! I look forward to scoping your channel. I like exactness and accuracy, and great to see someone who believes in such. Subscribed. Cheers
Many thanks. More coming along soon!
It gets more confusing when a parameter of a function is declared to be an array, because that gets adjusted to become a pointer. I.e void foo(char *bar); and void foo(char bar[]); are the same thing.
Useful, thank you. Out of interest I compiled the your code with gcc under linux. For anyone wishing to see what the compiler does generating assembler use "gcc test.c -O0 -o test -Wa,-adhln=test.s -fverbose-asm -fno-stack-protector" and "objdump -d test" and "hexdump -C test". I'm ten years out of practice with Intel assembly, hence mostly confused.
It's always a good idea to take a look at a disassembly (if you can understand it!). Maybe that's something I'll do in another video.
Thanks for the comment.
Huw
Good suggestion. I often do the same but I often crosscompile for another architecture which I prefer for reading. I disable -g because -g makes for pretty unreadable compiler output.
As for the K&R C book mentioned in the video. While I learned C from those books myself, I don't recommend them anymore. The C language has gone through quite an evolution over the years, several versions of the ISO C standard have been publshed over the years. The second release of the K&R book is documenting the C standard from 1990 and is commonly referred to as ANSI C. As partially described by the video the books are a little inaccurate in their language. Which is a pain if it's about the finer details but is probably making the books more readable. Compare to the actual ISO C standard documents which most people would refuse to read at gun point ;-)
If after all you decide to go for the K&R book, to those who speak German I suggest the German translation published by Hanser. The translators were very careful to be more precise than the original. Then again that doesn't make its content newer than the original K&R it was translated from. Finally K&R is a book for people who already know programming at least somewhat. It's not a “Programming for Utter and Total Dummies™”. Total newbies should look for other literature.
Makes me wonder, which modern C book based on the latest ISO C standard would you recommend?
I can recommend godbolt compiler explorer (it's an online tool) if you want to compare disassembly between different compilers or if you're just on the go
@@ralfbaechle Noted on the -g, I will try without. I am not sure if the book question is for me, but personally I would not recommend any book for a new skill. TH-cam tutorials are a much better bet to learn a new skill I think. Having learnt C I used books to learn sockets, pthreads and POSIX, but I would not learn something new starting with the written word unless I already had some vague competence in an area that was related. I learnt C on an AT&T 3B2 computer sitting at a Wyse-50 terminal (that dates it), I found the K&R book next to useless at the time, it has too much assumed knowledge.
An array isn't just an address because it knows its size. It's literally an array type. It decays to a pointer at the slightest provocation, especially in C, but it remains a distinct type. In C++ you can pass array types by reference and/or deduce them as template arguments, thereby keeping their size around and avoiding their decay.
That's not quite correct. The type and size information you enter for an array is not stored as data. The compiler uses that information to generate machine instructions when initializing the data stored in the array. The array name you entered, however, becomes actual data - an address. This is subtle stuff and you'd probably need to look at the disassembly to understand this fully. I think this is a common area of confusion and I'll make another video to explain in soon.
Best wishes
Huw
@@LearnWithHuw It is correct. As the standard says,
"An _array type_ describes a contiguously allocated nonempty set of objects with a particular
member object type, called the _element type._ The element type shall be complete whenever the
array type is specified. *Array types are characterized by their element type and by the number of elements* in the array. "
(italic emphases in the original, bold face emphases mine).
You can verify it yourself by using the sizeof operator. Like I said, all this stuff is much clearer in C++ because it has more powerful introspection capabilities, but there is no question that, even in C, an array is more than a mere typeless address.
Yes, you are totally correct here. In regards to @LearnWithHuw comment, while compilers don't generally store the size of the array in memory in the generated machine code, they can if they want. The memory layout like this is not prohibited by the language. But the compiler absolutely tracks the array size as it compiles as that array size can be referenced via sizeof or in C++ templates.
@@LearnWithHuw If you make a video trying to teach us something, then you should also bne receptive to being corrected.
@@ronald3836 I'm not aware of anything that needs to be corrected. What have you in mind?
If we are talking about semantics then an array is not an address - it is STORED as an address in program's executable, but an array is (surprise) an array with all it's contents, not just an address. And a pointer is just a variable containing memory address. So, if we are picking on things like this, lets be specific. You might ask "what's the difference?" Well, a value stored in a pointer can even make zero (nomen omen) sense, while array's address and contents is known at compile time and it's always there.
In C an array literally is an address. The "idea" of an array from the programmer's perspective may include the data stored at offsets from that address. But the array name you entered becomes quite literally an address.
Best wishes
Huw
@@LearnWithHuw well, if you say that an array is literally is an address then why don't you say that bool, int and short variables are addresses as well and make case out of it? They all are treated similarly by the compiler, so it's not fair if you call them differently for some reason. So: can we agree that all variables are stored as addresses, but they are abstract entities and should be treated as such?
And as a side note: if you ask me, the whole confusion comes from how index operator works and how similar is to use it for pointers and for array variables. And it is a valid comparison - they are similar in use, even if it's just some syntactic sugar.
@@viigihabe we can all pull that "abstraction border" back and forth to tell some "shocking" things, but the logic is merciless - if you not treat a variable as an address you shouldn't treat an array as such. And you will never compile a program like in your example with a modern compiler.
I don't understand all the confusion in the comments. I would think an array is just a sequence of data. So the program really just needs to know the start of that sequence of data (an address) once the compiler is done with it I guess.
It's just a sequence of data stored in memory with a start address to reference said data?
@@viigihabe I still don't get your point: I argue that variables behave similarly to arrays and you give me more arguments that support that point.
I used to read the C++ standard, while based on C I realize it isn't the same. Also it's been a while since I looked at it.... but I don't think you are totally right here. So point 1, an array is not a pointer... this is totally correct. But point 2, an array is not a variable. I don't think that is right. What you are seeing is the behavior when an array gets converted into a pointer and when it does not. This is why people confuse the two, because arrays convert into pointers in many scenarios. Also the lvalue and rvalue rules are strange and confusing but I don't think an expression not being a valid lvalue means it isn't a variable. I can not do (i + 0) = 0. This does not mean "i" isn't a variable.
Arrays *are* valid lvalues (you can use the '&' operator on them). They are just not *assignable lvalues*.
im a nurse teaching myself code who has no prior experience with coding and am a month in and this was the best way I have heard this explained thank you so much for this!
Many thanks. Good luck with your studies!
Best wishes
Huw
Answer: Fall for clickbait videos
Been a fan Huw for over 20 years. Was a delphi user. So pleased to see you still.teaching.
That's very kind. I take it you were a "PC Plus" reader? The Delphi columns were fun to do.
Best wishes
Huw
I like your C series
Many thanks.
It really helps to understand what your C code will be compiled to to write good code ... and it helps debugging!
I need to do some videos on debugging. I've discovered that some programmers barely if ever use the debugger.
This is fantastic. I struggled with this in my early days of learning C, since I couldn't build a good mental model around what I was seeing. Eventually I built a good enough mental model that I don't generally have issues with pointer decay, but this clarified a lot of things. Good stuff to think about!
Many thanks. I'll have more to say on this soon.
Best wishes
Huw
The mental model is that an array is really literally an array in memory. Just when you pass its value it becomes a pointer to the first element. Huw's view that it is an address is wrong, as the sizeof() of an array is not the size of an address.
@@ronald3836 sizeof is a compiletime function that stores the known values of your array initialization. An array in C, is, however, an address. This is a fundamental feature of C. As quite a few people are having so many problems understanding this, I will upload another video soon to explain this in more detail.
Best wishes
Huw
@@LearnWithHuw no, an array in C is the memory area with an array structure imposed on it. That using an array in an expression converts it into a pointer value to the first element (i.e. an address) is an important feature of the C language, but that does not make an array an address.
Another way to explain it is to go into detail about how the initilizer expressions are implemented.
With
void foo () {
char str1[] = "Hello";
char *st2 = "Goodbye";
}
The compiler will create a string constant in the program image containing the data contents of the string "Hello", and another one to hold the contents of the string "Goodbye".
Then, since these are auto variables, for st1, the function prolog (i.e. code added by the compiler whenever you enter a function before the user code is executed) will allocate space on the call stack to hold the array and COPY the characters from the initiaization code into the allocated stack memory.
For st2, the function prolog will allocate space on the stack to hold a pointer value and initialize it with the address of the existing "Goodbye" string.
Whenever you use str1 or str2 in an expression, the compiler will look up the symbol table entry to see what processing needs to be done and adjust its behavior accordingly.
Very clear, thank you
Thank you.
You might even say that _every_ identifier / named variable represents some address in memory (stack and/or heap), and pointers (specifically the & ) simply expose that address for use in code.
No.
Each variable has indeed an address, except for registe variables.
But the content of both a pointer variable and an array variable is (another) address.
@@maxaafbackname5562 The content/value of a pointer variable is indeed an address (= pointer). But the content/value of an array variable is the list of element values of the array. (Of course arrays cannot be passed by value, but they still have a value.) When you initialize an array, you assign it a value. E.g. int a[5] = { 1, 2, 3, 4, 5}; assigns the value { 1, 2, 3, 4, 5} (of type int[5]) to a.
It's why you don't use fixed arrays, but use pointers and allocate memory blocks for your arrays. and then you use pointers to pointers, voidpointers, functionpointers, and self modifying code :D
The real source of confusion is when you pass arrays to functions. Because then the conversion from array to pointer to a type is done automatically. So many people have the idea that an array is the same as a pointer, or that the compiler treats them as the same thing.
Plus it gets confusing when you declare it with array syntax in the function signature, but still isn't an array. But it is an array to C++ template typing.
I agree that an array is not a pointer, but it is not an address either. From the C standard (C99 and forward, the definition in C89 is more ambiguous) we get:
"An array type describes a contiguously allocated nonempty set of objects with a
particular member object type, called the element type. The element type shall be
complete whenever the array type is specified. Array types are characterized by their
element type and by the number of elements in the array. An array type is said to be
derived from its element type, and if its element type is T, the array type is sometimes
called ‘‘array of T’’. The construction of an array type from an element type is called
‘‘array type derivation’’."
As can be seen there is nothing in the official definition that say that it is an address, it is a contiguous allocated nonempty set of objects with a particular member object type with a known length. How compilers implement them can be by keeping internal pointers to it, or by literally copying the memory address to the first element, but neither the pointer nor the memory address is the array.
It typically has and address, but it is not the address, that is like saying that a house is the street address it is located at.
The confusion arises from the fact that "array" is used both to describe the high-level idea of a sequence of data items and the lower-level description of the array identifier/definition. When compiled and run, the array name literally is the address of the first item of the array (the sequence of data items). Use your debugger and prove it to your own satisfaction. This is clearly something that is confusing many people and I will do another video soon digging down even deeper (using a disassembly) to show this in even more detail.
Best wishes
Huw
Well, it still like saying that a street address is the same as the house located at that street address.
Think of it this way. The street address is the location where the house is built. House #1 is the first item in the street or in the array. The array address is the location of a piece of data (the first house in the street/array). But a pointer is like an envelope on which the address has been written. The pointer itself lives at another address. The array doesn't.
@@LearnWithHuw that is beside the point I tried to make. I want first to apologize if I came of as a bit grumpy, I woke up with a headache. What I tried to convey is that the array is not the same as the address to the array, it is the contiguous sequence of a single type, in your case a chars. You can create arrays in C that would not have an address at all, for example an array of 4 floats can in some cases be optimized by modern compilers to be a single vector register (such as xmm0) in the CPU. Since CPU register do not have a memory address such an array would not have an address. The best way to show that is to set it up in Godbolt compiler explorer (I tried to do that but I think YT don't like links in comments). For example the function below would be optimized this way with gcc 14 and -O3
float foo(float a) {
float array[4] = {0};
for (int i = 0; i < 4; i++){
array[i]+=a*i;
}
return array[0]+array[1]+array[2]+array[3];
}
@@tordjarv3802 I understand that this is a cause of confusion and I promise I will return to this subject soon. This entire comment thread has raised several points that need to be explored more deeply.
Thanks for the comments.
Huw
The local variables are created on the stack so are sized as declared so you can’t change that hence why the array can’t be reassigned, although the contents of course can be. The pointer variable is reassignable (as long as to a pointer of same size) as the contents are elsewhere in memory. Getting out windbg can be really illuminating
Actually, you got it wrong too. An array is not an address, which you can verify for yourself by calling sizeof. If you do this you will see that sizeof(str1) < sizeof(str2), assuming 64-bit system sizeof(str1) is 6 and sizeof(str2) is 8. This means that str1 is not an address, because it it were, it would have the same size as str2. However, in most expressions the name of an array decays to a pointer. But there are exceptions to this, such as when you call sizeof, typeof, alignof. What happens in your code is the call to printf forces the name of the array to decay to a pointer.
A great many people are making the mistake of assuming that sizeof is a runtime operator. It isn't. It's a compiletime operator and it simply stores the value of the array size as it was initialized. I will make a video in a few days to explain this.
Thanks for the comment
Huw
Sizeof(int) is not 1 neither. Which leaves the question: what is an address then? Address of a char as sizeof(char) is the only one that is required to return 1? Always, even if it is not true.
@@viigihabe The size of an address is sizeof(void*). sizeof(char) is 1 by definition, the address of a char has size sizeof(char*).
@@viigihabe sizeof is a compiletime operator. It doesn't provide any more information than is already known at compiletime. I'll have more to say on this in another video.
@@sturlamolden It was too late in the evening yesterday, so I came off with really bad presentation. Also I think I was in a middle of understanding what You tried to say and what @LearnWithHuw tried to say. I think that in Your perspective address is a pointer. 8 bytes in 64-bit systems and 4 bytes in 32-bit systems. In video he never stated clearly what he means by "address". My really bad representation of sizeof(char) etc was trying to show that address of an _object_ "char" is 1 byte (even if it is not) and address of an _object_ "int", although system dependent, but different than former. Pointer to those addresses is always the same thing: 8 or 4 bytes or even less in tiny systems. Lack of clear statement "address of an object" is the source of confusion here. By the way I was playing today a little with function arguments as arrays and I didn't know that before, but array sizes can be preserved. The usual f(char[5]) decays to f(char*) but f(char(*)[5]) doesn't. So, array as a type definitely exists. But then you have to pass an address of an array not just the symbol that presents it in code (variable). Address of an array that is. Btw, if you pass address of wrong-sized array the C-compiler will only produce warning (gcc at least does so), but c++ compiler (g++) will yell at you with full error.
One implication to keep in mind is that when an array is initialized with a string literal, a memory copying takes place. With the pointer variable, no such copying is performed. Opening up the Disassembly window will show that. For a short string like in the video, there will likely be a few register assignments, but on a larger string some form of memcpy() will be called. Something to keep in mind while initializing stack arrays this way - the memory copying will be performed on each call, which can hurt performance and use up stack memory (do it in a recursive call and you may run out of stack space).
I randomly came across your video and I have been using c/c++ to build network software for a few years now. I have nothing to add to the conversation as it was laid out in the video well, but I wanted to say I wish I were born a few generations before. I don't like the way we current write software and how much just horrible software is out there that is covered up by hardware power. The older generation of engineers were much brighter and I wish I had the opportunity to work and learn from them in person.
Thank you. Yes, there certainly is a generational difference in approach to software development. Many of us who are a bit older had to find out how programs worked the hard way (trial, error and lots of debugging). And, as you say, we couldn't afford to waste valuable resources like memory or disk space. Every byte counted. Still, you are clearly younger than I am and you seem to have the same feeling about the art of software development - so there's still hope! 🙂
Best wishes
Huw
@@LearnWithHuw Thanks for the response! Yes I am in my late 20's. I hope I can learn and carry the deeper knowledge into the next generation of developers!
If A is an array, then does the C Standard require that (uintptr_t)A == (uintptr_t)&A, or is that an implementation choice?
I suspect this is indeed an implementation choice.
So it could fail for compilers that implement bound checking and store the size of the array in the first bytes allocated for the array object.
If someone knows better, then please correct me.
A ÷= &A[0]
@@ronald3836 the sizeof a struct can be larger than the sum of the sizeof all of its elements (and often is due to memory alignment restrictions).
Conceptually the same _could_ have applied to arrays... except unfortunately Denis Ritchie, instead of coming up with a separate compiler operator to find the number of elements in an initialised array (For example something like 'dim(a)' being 3 for 'int a[]={11,22,33};') he decided to specify that 'sizeof(a)/sizeof(int)' gave the dimension. That arbitrary choice (which I'm sure made sense at the time) has locked us in to never allowing extra information to be stored in array (decide with haste, repent at leisure). So, in practice 'A' and '&A' will be the same address... although it needn't have been that way!
I'm a seasoned C programmer, even though it's a long time since I last used C. I have to admit, that I only halfway got it right. I got it wrong in that I thought of an array reference as a pointer. I got it right insofar as I would never mutate an array reference or expect such as reference to be a variable.
I developed a personal style during the later years of my C programming routine, which is a direct result of long debugging sessions. The parts related to this issue (arrays and pointers) are:
- always declare everything constant when it's not known to be mutated (lots of `const * const`).
- only use array notation when an array is created (stack allocation, value semantics)
- never mutate function arguments (unless necessary and prominently documented and (function-)named)
This goes to show that even if you spend a lot of time thinking about how to get something right, you still often have to pay a price. Here the price is to have to admit not knowing something as basic as this. Embarrassing.
Thanks for the comment. Getting things "right" in C is a constant challenge! 🙂
Best wishes
Huw
Pointers are the same as references, so an array reference is indeed a pointer.
Very enlightening. So an array is a hardcoded pointer to the stack (not a pointer variable). Thanks!
It absolutely is an address. I've just uploaded another video that digs down deeply to show exactly how this works (in memory and assembly) which you may find helpful: th-cam.com/video/bFAO99USrYI/w-d-xo.htmlsi=VMQKqSdyn1psWT0k
Best wishes
Huw
great video! I'll definitely use your book for my C study in the future.
Many thanks
What is true is that an array rvalue may be converted to a pointer to its contained type, without an explicit cast. That, in turn, is where the misconception arises. Using that as a shortcut to pass an array by reference into a function is pretty much necessary in C - there are no array parameters, only functions. Because that doesn't allow for conformance checking on arrays, that's a shortcoming of the language. Dennis once told me that he considered that the "single biggest botch" of C's design.
I think the confusion arrises with the behavior of decaying arrays, where all of a sudden it 'seems' like the array is a pointer because it behaves as a pointer.
#include
void doSomething(char* myCharPtr)
{
printf("%s", myCharPtr);
}
int main() {
char str1[] = "Hello";
doSomething(str1);
return 0;
}
The difference becomes even more clear when looking at two-dimensional arrays (as opposed to an array of pointers, or a pointer to a pointer, or a pointer to an array)
Yes, I was thinking earlier today that showing pointer arithmetic with a multidimensional array would make an interesting video that would probably be quite surprising to some people! I may do that soon. 🙂
@@LearnWithHuw that might escalate into a masterclass quickly because even the humble *argv[] requires an explanation of operator precedence :D
Thanks! Would be nice to have a video on arrays as parameters to functions and what happens there and the different ways that parameter can be specified: pointer to array element type, specified array length , unspecified array length.
I have a few more videos in preparation. Keep watching!
"Array reference" is the same as "function reference". All function calls actually operate on function addresses and function names implicitly converted to function pointers of their defined type. Implicit conversions are difficult to figure out, and all those "in other words" answers in books and on stackoverflow create even more confusion.
For the more advanced, the real confusion comes with array arguments for functions. Do you think you get the size of the array with a sizeof on an array argument? Nope. You get the size of a pointer. Fun stuff! 👍
Agreed. I'm still unsure whether functions should be declared with (array [], size) or (*array, size). I would prefer the first, but gave up on understanding the errors I would get. Thankfully, this video has rekindled my interest in learning the difference.
@@m3mb3rsh1p I was more on the side of using pointers as arguments, but my take has changed a bit as of late. One case where I'll use an array as argument is when said array is guaranteed to have a fixed size (a fixed number of items). Reason is that it self-documents code in a better way, and additionally modern compiler can give better results in terms of static analysis.
If you want to use arrays with an associated size parameter, from C99 on, the prefered way would be something like the following:
(size_t n, xxx array[n]). This is allowed, and is also better self-documenting IMO, while possibly again giving more opportunities for static analysis.
@@m3mb3rsh1p I believe that if you specify an argument as address ("array[]"), you would not be able to edit it, which makes sence in the scope of this video: you have an address, a number, and you can't change a literall. To be sure you must specify -std=, -Wall, -Wextra and -pedantic. I think compilers are good enough to detect that pointer ("*array"), if you pass it as argument instead, never changed, thus it can replace it logically as address, but that's all just C type abstractions anyway and it can be literally the same thing in the machine code anyway.
Haha, now Huw will say that a function in C is just an address.
The ANSI spec for C as with other ANSI specs for programming languages uses the terms "must" or "must not" and "should" or "should not". A must condition is something that the compiler and run time need to check for and prevent/diagnose. A should condition is just advisory to the C programmer that they are outside the boundaries of the language and also for the compiler and runtime. Many of the submitted bugs that our optimizer received were totally invalid because the C Programmer had violated a should condition. Writing valid C programs over a few hundred or thousand lines is hard. The standard answer to bugs where the program violated should conditions was that the C Programmer should write C code. Theoretically the code produced in such cases by the compiler could do anything it wanted, like erase all the users disks, although we tried to produce reasonable code in the cases that the optimizer detected. C vectors and arrays are ill designed.
I think another source of confusion in these examples is the use of strings and char pointers as the examples used. This brings into the discussion of arrays vs pointers a lot of the confusion around how strings are managed in C and by C compilers.
In some way it probably makes for a longer video if you used arrays of numbers as the basis, but it might make the final results easier to understand.
You may find my other video here, to be useful: th-cam.com/video/bFAO99USrYI/w-d-xo.htmlsi=ZiCXicV37MYKTJrd
That looks not only at arrays of ints but also at the assembly code to show what is happening at a lower level.
Best wishes
Huw
There is still some behaviour that i have yet to understand, specifically when it comes to passing in arrays as pointers into functions, or using arrays as function arguments. I don't remember the specifics, but i do remember struggling to pass in a char[8][10] as a char **, and i think i may have had to change the argument of the function to a char *arg[10]. But then you can pass pointers into functions with arg[] just fine, and you can pass char[] into functions that take char * with no problems as well. So I dont exactly know whats going on here.
Passing an array into a function that takes a pointer is called array to pointer decay. As explained in the video, an array is not a pointer so what you have is actually a variable with the address of the first array element (and you have lost size information, eg sizeof will no longer give the array size). But as far as you are concerned you can still treat it like an array because you can index it, and get what you expect. When it comes to multidimensional arrays, there is still only one level of decay. If you think about it, double indexing a char** does not work the same as double indexing a char[8][10], so it would not make sense for C to do this. Instead it will decay into a *char[8], which gives you the expected behaviour when indexing it - if you do not understand why I suggest looking into how the indexing operation (square brackets) works.
@@Coffe789That makes sense if we imagine that a char[8][10] is decomposed into a single char[80], but why could the char[8][10] not simply be "8 addresses, each of which have an array of 10 chars at the address". This is how I presume the *argv[] argument in the main function works, except that instead of 8 address to 10-char strings, there is an unknown number of addresses to strings of unknown length. How is that even possible?
@@Ryan-in3ot yeah so this is another scenario where the difference between an array and pointer matters. An array is contiguous memory, and if you have an array of arrays, you have a contiguous set of contiguous memory. So yes, in a sense you could think about it as a single char[80] that is indexed differently. You could absolutely have an array of pointers, that lead to other arrays, but that is not a char[8][10].
With argv, you are correct that it is an array of pointers; the characters are somewhere else in memory. The length of the array, and the length of the strings are not specified by the type definition, but you can work them out because you are given argc, and you know strings are null terminated. It is generally a good thing to let your array decay, and pass the length in separately, as it lets your function handle arbitrary length arrays.
@@Ryan-in3ot Because a 3x3 array of char is stored consecutively (i.e. 9 chars) for reasons of efficiency. For example when you have (char*)[3], you need 2 memory accesses to get the second char in the second row. This could well result in 2 page faults and 2 cache flushes. Whereas with char[3][3] the compiler knows that index [1][1] is just 4 characters from the start, hence the value can be fetched with one memory access.
The length of the argv array is given in argc, so there is nothing magic about it. You get pointers to the first character of strings, and they are null terminated, as usual.
Note that because it could be a varying number of arguments with varying lengths, it is not possible for the compiler to know the offset from the beginning when you give it a double index.
@@Ryan-in3ot char[8][10] is the same as (char[8])[10]. It is an array of length 8, where each element is an array of 10 chars. It happens to be stored in 80 consecutive bytes, but the right way to look at it is as an array of length 8 with element being an array of 10 chars.
To declare an array A of 8 addresses/pointers, each address pointing to an array of 10 chars, you need to write char (*A[8])[10];
Dennis Ritchie Was The Man!
Yes the compiler simply generates code that inherently contains the address of the array (regardless of whether it's on stack or heap), such that when you reference &array (or array[]), then that's the address you get? The same thing applies also with structures? (It's been a few decades..)
No, you can pass structures by value. You can also assign the value of a struct variable to another variable of the same type.
So if you insist on passing an array by value in C, I guess you could wrap it in a struct.
@@ronald3836 This wasn't always the case. In K&R structs (like arrays) could not be passed as values nor assigned with assignment statements.
@@corwin-7365 True, and it surprised me when I found out it was possible to pass structs by value and to return structs. It is possible that the first C compiler I used on my Amiga did not allow it.
what version of C are you talking about ? The reality is that arrays (even immutable stack allocated one) are still pointers. That's what they are. the[ ] operator is a de-referencing + offset, that's what it is. Of course the C type system is a little bit weird when it comes to arrays, as are the memory layouts. I feel its more a weak point of c, than an "arrays are not pointers" fact.
If you think about it terms of assembly or machine code, the two will be the same. Indexing is indexing. But to the C language itself, they are distinct types. The video shows how different expressions involving arrays vs pointers will print differently. Sizeof is another obvious difference. C++ takes the difference further with templates but that's C++.
An array is not a pointer. Try taking its sizeof().
@@ronald3836 C type system is a bit twisted like that. An array is an indexable memory zone though. That's what pointer are. You can declare a variable sized array at the end of struct. What would be the sizeof() of it ? Does it means its not an array ? Nor a pointer. What is it then ?
@@automatescellulaires8543 A pointer is not an indexable memory zone. A pointer is an address (with type information). An array is not an address. Therefore an array is indeed not a pointer (but the video gives the wrong reasons).
@@ronald3836 a void pointer might not be an indexable memory. A (any non void type) pointer is though. You can dereference it. And you can ++ it. And you can [x] it.
Wow. I am learning C right now and THAT clears a lot of things up. Thank you.
Glad to help.
It's less mysterious in assembler. Remember, C has all the power of assembler with all the convenience of assembler.
Ha! Yes, there is some truth in that! 🙂
So one could also say, that every variable is a short form of a typed 1 element array, and that a pointer is basically a one element array that can hold an address as typed value. In that way of thinking there is no difference between a pointer and any other normal variable (including pointers) that they all are just constant memory adresses to locations where a typed value can be stored. But only pointers are capable of storing adresses, and arrays are typically not except if it is an array of pointers. So, an one elemnent array of Pointers is equivalent to a pointer maybe, but an array in general is - like EVERY variable just an address of a location where a typed value can be stored.
Essentially this is true. Every variable of every type has an address at which some value is stored. An array declaration/initialization, however, allocates memory sufficient to store multiple data items. With a bit of jiggery-pokery, you could indeed use an integer as the base address for an array of sequential data items (though this is definitely not recommended 🥴)
I believe you should write "pointer variable" instead of "pointer". A pointer is an address/reference (to an object of a certain type). A pointer variable is a variable that holds such a pointer.
An array is neither a pointer variable nor a pointer.
There is only one slight exception I have - an "array variable" is in my opinion actually what K&R sought to implement, that is, a vector of modifiable values. in this sense, an "array" is a variable like any other base type variable. You can't change the address of an "int" either, for instance. I think the key is to make very careful distinction between "array" and "array name". When speaking of an "array", some amount of variable-content memory is part of its essence. Wonderfully cogent and stimulating video, thank you!!!
Just thinking out loud here, but would it not have been better simply to disallow the use of an array name without indices? p = &arr[0] is almost as easy to write as p = arr, and would make the whole thing consistent. You could then call an array a variable with no equivocation.
Thanks. I think people do often confuse the data stored at offsets from the address with the array name/address itself. So when people talk about "an array" they are often thinking of the data stored at offsets. This is probably more so now than it used to be as so many people come to C after learning higher-level languages such as Java or C#.
Best wishes
Huw
@@LearnWithHuw Of this there is no doubt. Same goes for those coming to it from FORTRAN and BASIC. I do think the way C does it is a kludge, evidenced by fact that "arr" and "&arr" are equal.
After all, isn't it wrong that &arr should be allowed? If arr, the array name, is an address, how can the address_of operator return a value at all?
@@karlkessler6017 Well, consider a statement like "int arr[5]": `arr` and `&arr` hold the same memory address but have different types. The former is of type `int *` whereas `&arr` is a pointer to the whole array, not just an element. The whole array has the type of `int[5]`. Therefore, in our case, the pointer has the type of `int(*)[5]` or pointer to an array of 5 integers.
Consider the following difference in pointer arithmetics (considering a 4 byte integer):
`arr` is a pointer to `int`, `arr++` will translate to `arr + sizeof(int) = arr + 4`.
But, `&arr` has the type of pointer to an array of 5 integers. `(&arr)++` will translate to `arr + sizeof(int[5]) = arr + 4 * 5 = arr + 20`.
I like this stuff that covers the fundamentals.
This is one of those things that keeps getting out of my head and I need a refresh. Thank you!
Many thanks. Glad to help.
Very well explained. 40+ years ago, learning from the 1st edition, I remember taking a bit of time to get this right and it's been clear ever since. Yet, as you said, many programmers find this concept very confusiing. Compiling various pointer and array definitions and references followed by subsequently examining the generated assembler can be very helpful in driving the differences home.
Thanks. Yes, I will try to help people to do this in another video soon.
But looking only at the generated assembler hides the type information from you. Without understanding the types of the various pointers, you have no good way of understanding what a change to your code will do.
array = group of variables (in the sense of variable contents) of same type, starting at a fixed address; name of the first variable in the array is var[0]
Watch out for my next video. I'll be digging even deeper to explain exactly how arrays are created and structured in C.
so... using %d to print a pointer (address) in a 64-bit build? There'll be an implicit truncation to int32 right?
The actual values shown are not important here. Just that some are the same addresses and others aren't. But you are right. 64-bit numbers are bigger.
@@LearnWithHuwplease use %p for pointers/addresses.
@@maxaafbackname5562 I typically do but I know from log experience that students who need to try to count a certain number of bytes from address1 to address2 often find the process much simpler when in decimal than hex.
I bought your books Little Book on Pointers and C programming. Waiting for delivery 📦.
Many thanks. I hope you find them useful.
Why not use %p, or at least %u, for printing addresses? Another important difference between pointers and arrays is the behavior under the sizeof operator. Sizeof(pointer) is the size of the pointer variable, usually 8 in 64bit machines, whereas sizeof(array) is the size of the allocated memory for the array.
"In C++ an array name is a constant pointer to the first element of the array." - SAM's Teach Yourself C++ in 21 Days; 1997, p293, Second Edition, written by Jesse Liberty.
Not always. E.g. when we call sizeof it is not a pointer. Jesse Liberty got this wrong.
Many C++ books have elements of the language wrong. I remember reading through ACCU book reviews where some books where criticized quite severely for being wrong in far more significant things.
My understanding, is that an array is it's own type, has it's own size and so on. However, arrays get converted into pointers very quickly and easily. In the video, the array is being passed to printf. Function calls are one common scenario where arrays are converted to pointers. It is a pointer by the time printf has it.
Well, books contain errors. C++ arrays are not pointers, but in rvalue contexts, array names are replaced by constant pointers to the array's first element (which BTW is not the same as a pointer to the array, although it has the same address).
@@__christopher__ That is true. A pointer to the array and a pointer to the first element have different types, but alias the same address. By aliasing rules the compiler is allowed to assume they are not aliased, and optimise according. This can cause havoc, unless the programmer is careful.
This is all rather silly. If an array "*is* an address", then sizeof on an array should always give the size of a pointer, which it only does if you try it on a function parameter that is declared as an array, for entirely separate reasons (ironically the practical reasons that give rise to the "special type of pointer" idea).
Even more ironically, it is those reasons (that an array 'decays to a pointer' when used as a function argument) that entirely explains the behaviour of printf in your examples.
As for the statement "an array is not a variable", what is that even supposed to mean? Arrays can certainly be mutable. While you can't use an array name as a modifiable lvalue, that's also true of struct objects (EDIT: that's actually wrong - struct objects *can* be modifiable lvalues, so in this sense arrays are not 'variable', only their elements)
An array is an aggregate, a collection of objects. Everything beyond that is an implementation detail.
An array is also an address that casts to a pointer. They're both addresses. I don't think it's ironic. It all makes sense given the time and context of the C language's creation. Especially given some assembly language experience. It's all addresses.
@@toby9999 An array variable is an array, i.e. a fixed-sized list of objects of some type, and it is located at some address.
You are fully correct, and this video shows that it is Huw that does not get arrays.
You are confusing array as a *** name *** in C code that used to access array type object with *** data *** that stored at the address which IS the array name. You can't change it, and it was demonstrated by taking a pointer with '&' operator from the array name, storing it as integer for printing and storing it as integer directly by name. It is the same, because array name is not an lvalue; it has no space allocated for it because it IS the space, like 5 is just a number 5 in C source file. Pointer on the other hand is an lvalue, and you can get a pointer with address where that pointer is stored by '&' operator. This is the same as function names: you can't edit them after you declared a function name because this IS the code. Their names are implicitly converted to pointer to functions when you saving them into lvalues and when calling them (since function call operator accepts function pointers, not the names).
@@rogo7330 A pointer is an address. I think the video confuses pointer variable with pointer. An array is clearly not an address but an area of memory (with an array structure).
"I love abstractions. I like the whooshing sound they make as they fly by.” ;-)
An array, is a pointer, but at a [completely?] different abstraction level ... ! Not that the virtual memory it points to is any realer ... I makes you think, doesn't it?
Interesting.
I once encountered an explanation of the code you've shown, where they said that the `char[]` is allocated on read only memory while the `char*` is allocated on read/write part of memory. It's interesting that in your demo, it's clearly visible that str2 is allocated after the whole memory allocated for the "Hello" characters.
Maybe this prevention of the user to rewrite the address of such Heap allocated array is part of some memory management that happens when we get out of the scope of the function?
In fact, a string assigned to an array (char[]) is in read/write memory. Look at this:
char z[] = "hello";
Here char z[] is in read/write memory. So I want to change a character, like this:
z[0] = 'z';
The result would be "zello". So this shows z[] is indeed in read/write memory but the string "hello" is in read-only memory. That's because "hello" is a string literal. It turns out that when you run the code shown above, the string literal "hello" is copied from read-only memory into read/write memory *at the address* of the array z. That address cannot be changed. However, the characters at that address and adjacent addresses *can* be changed.
I think we are getting into deep water here and this really needs another video to explain fully. I'll try to do one soon.
As for stack corruption. Don't assume C will protect you from this. You may want to my other videos on this subject that explain this in more detail. This one is probably the most relevant:
th-cam.com/video/X7uj_uGTOfk/w-d-xo.htmlsi=DXkms19vz2r9fHEX
Best wishes
Huw
String on the right of the array initialization is just a special case of initializer list. It is like creating a struct or an array of ints and just putting numbers one after another separated by coma. String literal also adds null byte at the end. So basically char a[] = "Hello"; is the same as char a[] = {'H', 'e', 'l', 'l', 'o', 0};
This is entirely compiler and system dependent. Some systems can store constant data in readonly memory or in write-protected pages. Not all can or do.
@@username7763 The type of a string litteral is required to be const char *. The pages do not have to be write protected though.
@@sturlamolden To illustrate your point, the following code generates a warning (and I guess potentially a crash when str[0] is being written to later):
char *str1 = "hello";
But
char str2[] = "hello";
compiles fine because the string litteral is "copied" into the array of char str2 (or rather, the array is initialised).
First. You stress out that array is only address. It's not true. At least it has type.
Second. It's notion / definition "quagmire". I don't know how in C specification called typed names. Those like variable, constants, array etc. But all of them have type, name and other properties like can it be changed and so on. I have urge to call them variables even if they are constant. My bad. From the perspective that it points to first element -- it's true. That's why someone metaphorically say it's just pointer. But you can't even say that it's constant pointer, because constant pointer has different behavior when you get reference to it. Also there are more weird things with multidimensional arrays like int arr[][5] or char **argv vs char *argv[].
Hm. First of all, thanks for pointing out the confusing terminology within the (academic) programming language community. :)
As you suggested, in academia one tries to be mathematically correct/meaningful. Of course there are still oversights especially in first editions or mix-ups in later editions when fixing only certain points. However, in my humble opinion, such forced distinctions are counterproductive and add to the confusion if the underlying concepts are not well understood.
For example, I can declare a constant pointer in C. Depending on the edition, this would be a constant variable, which makes little mathematical sense.
Technically, both are locations in memory, and from the programmer's point of view, it still makes sense to call them (and arrays for that matter) variables, while ignoring compiler optimizations etc.
That being said, I do, of course, agree with you that arrays are different from pointers in that they give you the address of the first element of the array without indirection. ;)
I am planning another video to dig down deeper into all this to look at exactly how arrays are compiled and initialized. Thanks for the comment.
Best wishes
Huw
Many of us still remember PC Plus. With fondness.
Ah yes. I gave them the best years of my life! 🙂
It is confusing, and not quite correct, to say that an array is an address. First, to be very, very clear, an address is a value that specifies a location in memory allowing a read from or write to that location. A pointer is an identifier which resolves to an address; the type of the pointer signifies what meaning to assign to the value at that address, in particular the size. In C and C++, a char pointer (char *) signifies the beginning of an array of char (a char** signifies an address containing a value that can resolve to the beginning of an arrayy of char) and is quite often functionally interchangeable with notation [] signifying a string constant. IIRC, you can indeed overwrite the storage of a string constant using a correct interface to do so, even in C. In any case, you certainly can declare, and use, a char[] of some size to use as a buffer, for both reads and writes. But in using a char* to address an array of char (aliasing), usually the semantics degrade to pointer-to-char. Notions of size go away, and the only boundary semantics in the language relate to the NULL char value. Hence many difficulties with buffer overruns.
You have to be very disciplined in the use of pointers. You always need to ensure the address value in the pointer is correct and, particularly, in bounds of any array in which it is operating; this is true for other types of array than char[], like int[]. C++ offers the possibility of consolidating buffers of some size with operations guaranteed (if you program them correctly) to abstract and enforce bounds, all within a class. Any time you pass a pointer value as a function argument, you can pretty much count on the loss of any associated array size, so a size argument is usually also required for element counts greater than 1.
Nearly all of the above babble may be best understood by looking at code well written to handle these things correctly, and sometimes with the help of drawing out the memory cells (paper, whiteboard) comprising the array being designed and managed. There is a reason that people writing code are paid better than people flipping burgers.
After watching this video, i actually came up with an adendum which could help solidify this concept, using some reverse logic: Imagine that the array *was* just a pointer. This would mean that the compiler would in fact, be pushing an additional value, the size of pointer, onto the stack in addition to the array itself, which would be undeniably redundant. I feel the reason why most people believe them to be the same, is because typically one has no need to "pass the array around" within the scope that it gets declared. Its only when it gets passed to another scope, that it *decays* to a pointer because you cant simply pass an array, since it doesnt inherently know how long it is after it is created, and has no choice but to pass a pointer. But whilst in scope, the space that has been pushed onto the stack for that array cannot change for the duration of the function call. Excellent video overall, very enlightening!
Many thanks.
An array certainly does inherently know how long it is after it is created.
Still, C does not allow you to pass around whole arrays. I suppose this was done for efficiency reasons (and to promote more generic code: no separate functions for different sizes of arrays, as Pascal requires). It would be possible to create a dialect of C in which you can pass around whole arrays (it wouldn't be C anymore, but you wouldn't have to rewrite that much of the language definition).
@@ronald3836 where in memory is the size of the array stored after its created?
@@bundlesofun9568 The compiler keeps track of it, as you are well aware. Just like the compiler keep tracks of the layout of a struct and of the signedness of integer types, etc. Are you saying C does not have signed and unsigned types just because the signedness or unsignedness is not stored in memory? Are you serious?
@@bundlesofun9568 in C, you cannot assign a pointer to int[5] to a pointer to int[6] because they are different types.
int a[5];
int (*b)[5];
int (*c)[6];
b = &a; // works
c = &a; // compile error
So the array "a" still carries the information about its size. Not if you use the name "a" in an expression, because it will decay/be converted into &a[0]. But it is there if you write &a to obtain a reference to the array, i.e. a pointer to the array object.
And we are talking C here, not assembler or machine code.
Hmm guess I should get edition 2 lol.. Very nice video!
Many thanks
I still don't understand why is it impossible to place different values to array being sure stay in boundaries. Well, I realize it is just a C rule.
So the main problem is in the way array is initialized. It uses the same symbol as valuable assignment does. Using '=' thing for both operations confuses inexperienced programmers. Declaring array is not just a putting value to memory, but complex activities with addresses and so on.
Thought a bit and figured ou that there's not just '=' in array declaration that works, but '[]=' ! Brackets belong to operation, not array name. They ARE operation.
i think they want to emphasize array is a block of memory contains array of some data structure cells like struct don't treat pointers to array as if they were pointers to primatives, take care of this memory block internal structure
Every single thing in memory is a pointer. If you have programmed bare silicon in asm this is obvious. Once you understand the concept its perfectly logical and easy to understand. If not you are not a c programmer.
So please explain how to access the individual elements of the array pointed to by str2? Not str2[i], right?
What is missing in this video is constant pointer. It would be interesting to see how it is treated by the compiler. And probably you should add that dynamically allocated array with new is still a pointer.
When I think about arrays and pointers being the same I think of where I can pass it to. My C/C++ is rusty, but from what I remember if you have a parameter that's an int*, you could pass an int* variable to it or pass an array. Or the array maybe needed to do something like &arr[0] (couldn't remember if you need to do that or if that was just something from example code playing with this years ago). I'd never thought they were the same before you'd pass it in to something's parameter. The other important similarity is you can iterate over the array elements with pointer math. int *pInt = arr; if (*(pInt+1)==arr[1]) { printf("the same"); } else { printf("different"); } If the array is filled with unique values that blurb should print "the same" and you can trust it wasn't dupe values happening to line up.
If you pass "arr" as a function argument, then that is equivalent to passing "&arr[0]".
If you really want to pass a pointer to the array object that is named "arr", then you should pass "&arr". The value of this pointer is the same, but now it is a point to the array object instead of to the first element only.
You are correct that an array object is not the same as its address.
@@ronald3836 For a datatype[x], I thought arrays in C were just a contiguous chunk of memory that could hold x datatype items end to end. So there isn't really an array object with extra state beyond its items. Even if there's item padding (#pragma pack or something??) I thought the padding was always after the element data, and I thought also that padding was counted in sizeof (a lot less sure about that).
@@MikeIsCannonFodder Indeed there is no further state, but the proper mental model is that the array is that chunk of memory and its structure (number of elements and type of element).
A peculiarity of arrays in C is that the name of an array is in nearly all cases converted into a pointer to the first element of the array (but not when & or sizeof are applied to it).
@@ronald3836 For your last sentence are you talking about dynamic arrays from malloc? I've only been talking about static arrays on the stack. If you are talking about dynamic arrays, then that makes sense since you'd be getting a pointer to a pointer.
@@MikeIsCannonFodder I am also talking about statically allocated arrays.
In C, if you declare
int array[10];
then using "array" in an expression is in most cases equivalent to writing "&array[0]". For example, "array + 1" is the same as "&array[0] + 1" (and is the same as &array[1]).
This is not the case if you write "sizeof array
- sizeof array = 40 (if sizeof int = 4)
- sizeof &array[0] = 8 (on a 64-bit system).
This is also not the case if you write "&array":
- &array is a pointer to the array object: it will normally point to the same address as &array[0], but it does not point to an int object (of 4 bytes) but to an array object (of 40 bytes).
- &&array[0] will not compile.
I only have one correction: A constant pointer is also not assignable after declaration, and thus it is not a variable. So your dichotomy "pointers can be assigned and are variables" vs "array is not that" is also confusing.
This is good to know but for me obe(overcome by events) as while working in more modern c++ unless absolutely necessary I am not allowed to use c style arrays and pointers.
Thank you so much Huw. Now I know where my misunderstanding is coming from.
Glad I could help!
Constant in C/C++ variables is possible to be changed by using memcpy. Dont know if it is pros or cons of C++ haha
I love your videos brother. Keep up the incredible work.
Many thanks. Much appreciated.
Excellent! So I understand some of my errors from the past!
Many thanks.
@@LearnWithHuw after many l-value error, I used pointers for everything, instead using arrays when possible.
For many years!
When the sizeof(foo) != sizeof(&*foo) you probably have an array of foo.
Multi-dimensional arrays (such as int[5][6]) will always require exactly one indirection. Ragged “arrays” (such as int **) will always require more than one indirection.
C does not have a concept of multidimensional arrays. What you have there is an array of arrays. As for ”ragged” arrays they only require one indirection as well, at least in mot algorithms where we iterate along the data. Proper C code will either use a pointer as cursor or copy an element from the pointer array into a register. Then this pointer is indexed like an array, but kept in a register for multiple indexing. Both methods tend to yield single indirection, even for ragged arrays. If you se double indirection in numerical C code, it is a clear sign the programmer was incompetent and should have used Fortran.
@sturlamolden C definitely has multidimensional arrays. An example is int rotationMatrix[3][3]. All nine values are clustered together in one contiguous memory area, accessing any element requires exactly one indirection. Ragged arrays (like main's char *argv[]), is different. To access a specific character in an argument, the CPU has to do two indirections, one to get the address of the target argument and another to get a specific character in that string. (ex: char firstLetterOfFirstArg = argv[1][0]). rotationMatrix[0][0] will cost one indirection, while argv[1][0] will cost two.
@@breathermachine2 Only an amateur would write argv[1][0], argv[1][1], etc. and incure double indirections for each letter. You start with const char *arg = argv[1]; This is an indirection, but you only do it once. Now you get all the chars in the first argument with a single indirection, from the pointer arg. Then, repeat this. You do not get double indirections for each char. If there e.g. is 10 chars in the first argument you get 1.1 indirections per char, not 2. In C code though it is more common to see the coding pattern const char *arg = *argv++ instead of indexing when stepping through a ragged array. And then you iterate over the chars using a while(c = *arg++) loop. Then repeat arg = *argv++, repeat the while(c = *arg++) loop, etc. This does not produce double indirections on each char. However, if you write argv[i][j] you do, but then you are displaying amateur level knowledge of C.
As for your rotatationMatrix it is an array of arrays. It is not really a 2D array. It becomes contiguous in memory because in C one declaration gives one contiguous block of memory. The row-major nature of C is actually not a convention, like the column-major ordering in Fortran, but rather a byproduct of rotatationMatrix being an array of arrays.
Which is faster? A novice would say the array of arrays, because is incurs less indirection on average. However, to accelerate numerical code the use of SIMD instructions require special data alignment. Intel’s AVX instruction set requires 32 byte aligment. How can we ensure this? The ragged array makes this simple. We simply overallocate each row ro fit the data into an aligned block. And while it has more indirection on average (or only alightly so), it can allow the compiler to use more advanced instruction sets. On a processor with hierarchical memory, the cost of a flop dwarfs that of an L1 cache lookup.
@@sturlamolden Call me amateur or whatever, my point still stands. I'm not sure what point you're even trying to make. intMatrix[0][0] is one indirection, argv[1][0] is two. That's it. I know all of the things you mentioned, I'm no stranger to pointers/arrays. So now you tell me, what exactly did you correct in what I said?
@@breathermachine2 It is correct that argv[1][0] isolated incurs double indirection. However, this is not true for algorithmic code on ragged arrays in general (unless the programmer is incompetent). When we have multiple data access along a row, the double indirection can be amortized to single indirection.
Sold
By a some reason when I listened to this, it reminded me about Brick Top monologue from the "Snatch" movie.
Great video!
What is that IDE / interface you're using?
Is that "Immediate Window" a sort of interactive compiler or is it a wrapper around gdb?
This is Visual Studio. Its debugger is great and the Immediate Window is just one of many useful debugging tools.
Best wishes
Huw
I was expecting something like strict aliasing, not something trivial.
There is a problem in your explanation in my opinion. Now I could be wrong , because i've programmed in C quite a long ago.
The Example you've shown have used String Literals as an Array Object. But Compilers Put Any String Literals initialized into an array in "Code/Text" Section of the binary (.pe / .elf ).
I think you should use An array of integers with a fixed number of elements and use scanf() to put the values into them.
And Then Compare Them with an Pointer to an int.
I think that would be a fairer comparison.
It's the same. The array is initialized with the stored characters.
I guess there is something to be gained by owning the second edition ofthe book. I have the first and I thank you for making this video to clear up that ambiguity.
There are several interesting changes between editions 1 and 2. I may take a look at some others in future videos.
Best wishes
Huw
i always get scared doing things like char *str = "Hello"; because i dont know if that is guarenteed to allocate 6 characters on the stack. Im not even sure if the stack is where these string literals go, but they seem to be modifiable in both the pointer and the array case, so i cant imagine another place they'd end up.
I've learnt this from cs50. When you use char *, the array is terminated by a '/0'
string literals are stored almost as global array variables. On some platforms those memory sections are read only (like instruction sections), on others it's read/write-able.
Consider this example:
char global_array[ ] = { 'G', 'l', 'o', 'b', 'a', 'l', '\0' };
int main( ) {
char local_array[ ] = { 'L', 'o', 'c', 'a', 'l', '\0' };
const char* string_literal_pointer = "String literal";
}
global_array is stored in global memory and is guaranteed to be read/write-able.
local_array is stored on the stack and will be 'created' when the main function starts and will be 'destroyed' when the function ends.
string_literal_pointer (remember, pointer is just an integer variable) is stored on the stack and is pointing to some global memory area, where the characters are located. I don't know for certain, but it's illegal to modify the string literal data (as far as I can tell). It's probably platform specific, but is always assumed to be read-only.
Practically, it means that there is some space in the process's memory dedicated to the stack. The program would keep track, how much of stack space the program is using by having an address of the top of the stack somewhere in memory (in RAM or in CPU registers). Your compiler then would count how much memory it would need to store all local variables of each function call (plus some call related low level data) and would just increase the stack pointer by that amount on each function call, thus claiming that chunk of memory. And when the function call would end, it would decrease the stack pointer by that amount, thus releasing that chunk of memory. In that sense, a local variable is just an offset in that chunk of stack memory.
Global memory, however, is always there; it's size is calculated at compile time and is given to the process when the program starts. It's never created or destroyed
The str variable itself goes on the stack. It's a pointer, so it contains an address that's likely 64 bits nowadays, so 8 bytes go on the stack. For the characters themselves, it depends. In a function, it goes on the stack, so that it will disappear when the function exits. Unless you declare it static or if it is a global variable. Then the value must exist for the lifetime of the program and the compiler will store it in a data segment. This is neither the heap nor the stack. It's a section of your executable file that gets loaded in memory at startup.
@@ErikBongers this is an optimization tho, one could pass the address of the string literal to be used somewhere else, thus it 'must' have global storage. So, you shouldn't rely on it to be allocated on the stack
When you do `char *str = "Hello";`, the string literal "Hello" (and the null terminator!) is stored within a read-only section of memory inside your program. Note that it's read-only. The proper type is therefore `const char *`, but many programmers ignore that.
When you do `char str[] = "Hello";`, the array `str` exists at compile time but the string literal is copied into it, so you can modify it safely
$str1 working is a a bug in c
a constant is a value-locked variable ?
No. A variable’s value can vary. A constant’s value is fixed. It makes absolutely no sense to say that a variable’s value is locked, because that means it is not a variable anymore.
P.S. if you’re struggling to find a general term for these things that have names in programming languages (and subsequently trying to use the moniker ‘variable’ for everything) just remember that as far as the compiler is concerned, all names are symbols. Terms we use like variable and constant (or macro or function etc) merely define what that symbol means.
The most stupid thing in explaining C is to hide from the user that there is lvalues - things that must have allocated memory for them somewhere, and non-lvalues - things that are just information, like number 5, that don't have (or don't have to have) allocated memory for them since you can't edit them anyway. Array names, function names, and something else that I totaly forgot - they don't have (or don't have to have) a variable on the stack to store their address since you can't change them, as you can't write something like "5 = 2 + 2;". I hate when someone says "decays to a pointer" instead of "C implicitly creates a pointer with this address". Wtf is "decays", how it's done, what are the effects? Conversions are well defined (until they are undefined, lol), and "decaying" is just a made up word for things that C does with addresses.
Arrays *are* lvalues, since you can apply the '&' operator to them (which can only be applied to lvalues). Ie:
int a[10];
int (*p)[10];
p =&a;
is perfectly valid.
Arrays just happen to be lvalues that can't be assigned to.
Note that in K&R structs were *also* lvalues that couldn't be assigned to... however that restriction was lifted in later versions of C.
What about constant pointers (not pointers that point to a constant)?
I mean the ones thats declaration looks like
some_type *const ptr;
in the simpest case.
How do they differ from array variables?
I suspect that an array variable is more than just an immutable address to the starting address of a memory area of an array, like a constant pointer (value), since the compiler keeps its size in check. Otherwise the sizeof(a) / sizeof(a[0]) would not work to obtain the length of an array with identifier a .
They are immutable pointers so not quite the same as an array which is not a pointer at all but literally an address. That is, if you define an array called myarray, by the time the program runs, that identifier has been replaced with an address.
Best wishes
Huw
Arrays and pointers have the same decay type. This is why it's a lot easier using them in C++ where you have std::decay and the like to make it more explicit (and provide a more uniform calling syntax). Of course in C++ you shouldn't be using arrays or pointers either, but that's another story.
Interested in your comment about not using pointers or arrays in c++. What would you advocate in place of them? Not having a go at you. Just genuinely curious 👍
I guess he is referring to the fact that usually you would use a data structure that 'wraps', sort to speak, an array, such as a std::vector.
And for pointers he may be referring to smart pointers.
And with 'not using pointers', they probably mean: not using owning pointers, for which you'd have to manually call new/delete/alloc/free, you'd want to use a RAII wrapper that guarantees the delete/free is called in the destructor (it can't be accidentally skipped because of some error condition for example). But using non-owning pointers, i.e. a 'reference that can be null' is perfectly fine, as long as you know it can't become a 'dangling pointer', if you can't guarantee that, a shared_ptr with weak_ptr combo probably more appropriate.
@@NotMarkKnopfler For fixed-size, typed arrays, you use std::array. That has the advantage of carrying its size with it. But std::vector is better as it is move constructible but still has the same memory contiguity guarantees as std::array. As for pointers, unless you're taking the address of a stack-based object, in C++ memory should be handled with std::shared_ptr, std::unique_ptr and std::weak_ptr. These have reference-counted or RAII-style lifetime behaviour, so leaks are much more easily avoided.
It made no sense until I realized you were saying POINTER, not PUNTER. I thought PUNTER must be some insightful football term to refer to a role yet to be explained to non-footballers. I suppose it ended up well though and my Americanized ass ends up admitting that Rooney was one hell of a punter. In fact, normally in American football a punt goes high in the air trying to achieve "hang time". Rooney had none of that. Canon shots right to the proper address.
And here I was going to guess "Bounds Checking".
And another question. We still can address array as a pointer and alter it's values. How does it work?
This may be clearer when I upload my next video. But in essence you need to keep in mind that the data stored at an address and offsets (contiguous addresses) can be changed but the address itself (the start of the array) cannot. I'll explore this in much more detail in my next video.
Huw
This comes down to how compiler works. It treats the array as a constant, so it forbids you to change the array (you can change the individual elements however) before even program is compiled.
On the other side, pointer assignment happens during runtime, outside of compiler's control. A pointer variable just points to some memory location, and you can do anything you want with it as long as your operating system/memory manager doesn't complain. In the examples above, the array str1 and pointer str2 are allocated on the stack, so if you alter str1 value using a pointer, you risk overwriting whaterver else lies after the array str1 in memory, including the value of the pointer str2.
@@LPTech-m5m You can never change the address of a variable, whether it is an array variable or a simple int variable.
If a variable is allocated on the stack or the heap, it will have an immutable address.
If a variable is stored in a register, then it will not have an address.
This may be the case for array variables stored in AVX registers, which shows that an array certainly is not the same as an address.
@@ronald3836 Dude, that wasn't the point of this video.
@@LPTech-m5m Just pointing out why one cannot change the address of an array. It is for the same reason that no variable allows for its address to be changed.
But what is the point of the video according to you? Seems to me the point is "array in C is an address" (which is a false statement).
As a C++ develo[per it is interesting to get the C only perspective as it means the same thing effectively but the language used is different without reference to const or decay.
It would be interesting to continue to talk about the array of characters themselves, such as str1 is on the stack while str2 is in the text segment thus the vastly different addresses. Presumably, although bad design, writes through str1 would only have a lifetime of the stack frame / function invocation while writes through str2 would either modify global data or perhaps fault depending on the operating system / write permissions to the area where the str is stored.
Thanks for the comment. The comments in this thread have given me quite a few ideas for other videos so I'll be returning to this soon.
Best wishes
Huw