2020-03-18 23:18:39

Check post 24 again, I re-edited and after I realized what had happened.

A winner is you!
—Urban Champion

2020-03-18 23:22:18

Ah. That makes more sense.

2020-03-18 23:23:10 (edited by camlorn 2020-03-18 23:24:54)

@23
This is wrong. Specifically compare the return value to 1. Scanf will return non-zero values for errors which will be counted as true.

Edit: Missed the not.  But that's still wrong, because EOF is usually a non-zero value.  Compare to 1. Anything but comparing to 1 is wrong in this instance.

My Blog
Twitter: @ajhicks1992

2020-03-19 00:07:18 (edited by Ethin 2020-03-19 00:08:09)

@28, I could've sworn that no comparison was equal to ==1? I.e.: if (scanf("%d", guess)) == if (scanf("%d", guess) == 1)?

"On two occasions I have been asked [by members of Parliament!]: 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out ?' I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question."    — Charles Babbage.
My Github

2020-03-19 00:58:01

Oh, my bad. Will fix ASAP. Will keep the original post intact for post history sake.

2020-03-19 02:53:03

@29
No, it's not. True in C is any non-zero value including negative integers.  This is why you can if(x) to check if a pointer is non-null for instance.

My Blog
Twitter: @ajhicks1992

2020-03-19 05:09:36

@31, thanks. smile

"On two occasions I have been asked [by members of Parliament!]: 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out ?' I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question."    — Charles Babbage.
My Github

2020-03-20 01:41:18

I might as well revive this, because I have another issue and I have no idea what to do to solve it. Suppose that a chunk of code looks like this:

int validity(int board[], int x, int y) {
if (board[x][y] < 1)
return 1;
return 0;
}

Very simple, right? Except my compiler cannot compile this. It complains that an array subscript must be an array or a pointer type, error 2109 if I’m not mistaken. What does that mean? Why doesn’t this work?  Googling this brought me to similar problems, except I am not too sure as to what they were doing differently, their code looked pretty much the same when they claimed that it was fixed.  Also, it was in C++, but, you know. Small details, right?
I’m sorry for the lack of indentation. Type in this on iOS, as I am not currently at home.  If I slightly messed up the syntax on here, my original code had the error I mentioned.

2020-03-20 03:10:36

You'll probably want to pass the board as a pointer along with the width and height, then do int value = board[x*height+width].  You can't pass arrays by value; if you want to do that, you'd do something like:

struct Board {
    int *data;
    size_t width, height;
};

int readBoard(struct Board *board, size_t x, size_t y) {
    return board->data[board->height*x+y];
}

then use memory allocation functions to allocate board.  ALternatively, put the array in the struct, which stops a thing called pointer decay from happening.

You want the width as well because that will let you check whether x is right--in practice you'd have a debug-only bounds check here with assert or something.

I had to look up the semantics, but basically arrays are converted to pointers when passed to functions, and int board[] is just a fancy way of saying pointer to int.  I had to look it up because it's not done in practice, since (in C at any rate) you can't make a function that works on any size of array.

C and C++ are different as well.  (most) C compiles as C++.  Very little C++ compiles as C.  C++ is something like literally 10 to 20 times as complicated as C.  I'd not try to get C++ resources teaching you C for that reason, though in this case you'd have the same problem in C++ as far as I know.  C++ people have a set of solutions to this that are sort of better, but explaining non-type template parameters is very much not worth it until after you've had 5 or 6 other explanations of C++ things.

My Blog
Twitter: @ajhicks1992

2020-03-20 03:53:50

Ah. Pointers. Something which I’m not really familiar with.  The assignment was to create a tic-tac-toe game by separating it into functions. For me, it wasn’t worth it, but hey. Better humor the instructor, right?  Pointers is something we will get into shortly, though.
I know I was supposed to have a check if X is out of bounds there, but I was going to add it later, when it actually, you know, compiled.   I studied Structures on my own, and from what I can understand they are like classes without functions, that is, whole data but cannot act internally on it.  Would my definition be correct?

2020-03-20 04:32:35

You can put the array in a global variable (i.e. outside of any function) and then all the functions can just use it.  if this is a college/school setting, I doubt your professor intended for you to start trying to pass multidimensional arrays around without a proper explanation of pointers.  In C, arrays are actually barely better than raw memory, and they're not super useful for encapsulation unless either in a struct or used as pointers.

If you want to try to understand, consider how you'd write a function that took an array of any size without being passed the size while the program is running.  I don't know how to put this particular insight into words, but it's an important one to grasp.  Start asking yourself how the language has to implement the thing, and you can start getting a feel for the why of C and not just the how.  C never does anything for you, ever.  If the answer you arrive at is "the compiler would inject magic instructions to make this work for me" that's usually the wrong answer.  The situation with arrays in C could be a little less confusing, but it's actually damned near impossible to make it useful without the compiler doing magic for you at runtime or some advanced features that C doesn't have for describing types.

My Blog
Twitter: @ajhicks1992

2020-03-20 06:27:23

If I know the array type, though, can’t I do something like this?
sizeof(array) / sizeof(arrayType)
To find the length?

2020-03-20 11:14:11 (edited by Ethin 2020-03-20 11:15:52)

@37, no, that only works on static arrays (allocated during compile time), and isn't actually correct. This is how you do it for static arrays:

//...
const char* versions[5] = {"1.0", "1.1", "1.2", "1.3", "1.4"};
// sizeof(versions) / sizeof(versions[0]) == 5

This, however, is not correct, and is an error in GCC (-Wall -Wextra -Werror):

char** versions = malloc(5*sizeof(char*));
for (int i = 0; i < 4; ++i)
versions[i] = malloc(4*sizeof(char));

strncpy(versions[0], "1.0", 4);
//...
// sizeof(versions) / sizeof(versions[0]) == error

Notice the use of the malloc and strncpy functions. If you don't know, malloc allocates uninitialized memory, and strncpy copies characters from a source to a destination. strncpy is a (slightly safer) alternative to strcpy, which is less safe and does no bounds checking. strncpy requires that you pass in the length of the string your copying, and will only copy the amount you specify. Same with functions like snprintf vs. sprintf and strncat vs. strcat. On the links I've pointed you to you will notice functions like strcat_s, strncat_s, etc. Don't use these functions; only Windows actually implements them (and they don't exactly offer any advantages, though they claim to). You can either start using functions like malloc/strncat/snprintf now or later; you might've already started.

"On two occasions I have been asked [by members of Parliament!]: 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out ?' I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question."    — Charles Babbage.
My Github

2020-03-20 16:28:04

@37
Additionally, though sizeof tricks (in some limited circumstances) gives you the size, what is the type you have to give the function?  Array types have to have sizes if they aren't decaying to pointers, so if it takes an array of length 5 you can't pass it an array of length 6.  Even C++ has this restriction, though in the case of C++ there's a facility for "make copies of a function on demand for me" that lets you instruct the compiler to generate them.

Also, what if the array has 2 dimensions?  Dividing out with sizeof doesn't work there because it gives you the total number of items, not the number of items in each dimension.  Maybe it's 4 by 4. Maybe it's 8 by 2.  You don't know.

When people write C codebases they tend to write their own array wrappers to get around these problems by doing math at runtime.  In C++ you have this as std::vector, but in C everyone does their own because of the highly minimalist philosophy (and also, by the time you're in C, writing your own matters because you probnably care about performance, and there's a lot you can do to optimize for your specific problem).

I would strongly suggest moving onto pointers and coming back to arrays later once you understand pointers.

My Blog
Twitter: @ajhicks1992

2020-03-20 22:45:36 (edited by amerikranian 2020-03-20 22:48:44)

So I have taken Camlorn's advice and been looking at pointers off and on today. They don't seem too terrible from a conceptual standpoint. My questions are below:
1.   What happens if something like this gets executed?

int number = 5
int * pNumber = number;

Obviously, we're missing the & before number, but what happens when I try to dereference pNumber? When I tried to print the value of the pointer, the program just froze for a bit and exited without any complaints. No output was shown, which is expected, but no error messages were raised during compilation or runtime, either.
2. Do I always, always, always have to do this even if I know with 100% certainty that my pointers will never be NULL?

int number = 5;
int * pNumber = &number;
if (pNumber) {
    //...
}

3. What is the use of having constant pointers? I.e

int number = 5;
const int * pNumber = number;

It seems pointless because I can still change what the pointer points to, I just can't change the actual address of the pointer, i.e

//Combined with our previous example
int number2 = 6;
pNumber = &number2;

When would I ever use this?
4. Going by the same note, why would I use the following?

int number = 5;
int * const pNumber2 = &number;

Yes, that is perfectly legal as far as I know.
5. So I now understand what y'all meant when you said when saying pointer to array relationship. Trouble is, I am now confused as to how to loop through an array

int values[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int * pValues = values; //Points to the first element in the array
//Now, I can do this:
for (int i = 0; i < 10; i ++) {
    pValues ++;
    printf("%d\n", *pValues);
}

That will go through the array, but it will also add in another variable, i, which I might as well use to subscript the array to access the values. So, what's the point of having a pointer?
I can't do this:

for (; pValues < 10; pValues ++) {
    //...
}

From what I gathered, if I just type in the pointer name, the compiler will think that I am trying to access the address in memory the pointer points to (a hexadecimal representation which will not equal 10).
So what am I missing here?
That's as far as I've gotten today because of the headaches of the const int * pointer and int * const pointer. Oh, and you can also do const int * const pointer, too, which doesn't help me at all when trying to process the information.

2020-03-20 23:11:07

You're conflating several concepts here.  I'm going to split this across two posts, one covering pointers and one covering const, because const is a separate thing.  Unfortunately the answers don't line up with your questions, so do me a favor and ask any further questions you still have, and I'll do my best to tackle them.

When doing an array, I suggest:

int numbers[] = {1, 2, 3, 4, 5};
const size_t NUMBERS_LENGTH = sizeof(numbers)/sizeof(numbers[0]);

Now the thing is:

p[i]

is the same as *(p+i), that is add the integer i to p's address (moving it forward in memory by 5 ints or 5 floats or whatever else--this isn't bytes, it's the size of the pointed-to thing) and then take the value.  So even if a is a pointer, you can still do:

for(int i = 0; i < a_length; i++) {
    int item = a[i];
    // stuff with item.

Second: no, you don't always need to check pointers to find out if they're null.  Pointers never become null unless you set them to null, so as long as you know it's not null you're fine.  That could be that you just pointed it at something, but it could also be that the caller of the function just isn't allowed to pass null in, or whatever else.

When setting a pointer to a number, instead of the address of a variable, you're pointing that pointer at that specific byte in memory.  It is fine to do that, as long as you don't dereference the pointer, or have some other reason to believe the pointer is pointing at valid memory.  C has a concept of undefined behavior, which is to say that it's behavior that can do anything.  When you read a pointer pointing at address 5 for instance, you could crash, you could get garbage data, your compiler could go "this can't be valid memory, let's optimize this read out", you could be talking to a device instead of memory, etc.  yes, hardware devices get addresses in memory, though usually your OS prevents you accessing them.  The only time doing this for real is a good idea is if you're doing kernel-mode programming, OS work, or embedded programming--in that case there are hardware-provided magic addresses that do things when you read from or write to them.  Otherwise it'll range anywhere from inconsequential random data happens to the OS crashes you for an invalid pointer read, and what exactly happens will even change run to run of the program.  In other words: don't do that.

You say the program exited without any complaints.  Depending how you were running it, it probably actually exited with a complaint, but that was hidden by being in the terminal and you didn't examine the exit code.  It can't work though, no.

Now to circle back to the loops.  You can actually compare pointers.  p1 < p2 if p1 points before p2 in memory.  You can increment pointers: p1++, p2++, etc.  You can subscript, as I did above, even if it's a pointer.  But to maybe try to help you get this, here is a function which prints a string:

#include <stdio.h>

void printString(const char *s) {
    for(; *s != '\0'; s++)
        putchar(*s);
}

If you don't know yet, strings are segments of chars in memory, and the last character of a string is always 0, which we write as '\0' (note the apostrophes which are silent with some synths) because it's a character, not a number.

My Blog
Twitter: @ajhicks1992

2020-03-20 23:27:55

So, part 2 is const.

The idea of const is that you are promising that a variable (or a pointed-to thing or etc) won't be modified.  There's two ways of using const:

const int x;

and:

int const x;

The first is convenient shorthand for the second, which will also apply const to all the variables on the same line, but for the sake of explanation (though not really for the sake of real code) we're always going to use the second for the moment.  The rule is that the first kind of const gets rewritten to the second kind of const, and then const affects what's immediately to the left.  So, some examples:

// a constant int.
int const x = 0;
// or:
const int x = 0;

// A pointer to const int.
//This is a promise that this pointer only reads data. You can change its address, but you can't write through it--the comiler will stop you:
int const *x;

// A pointer that can be written to, but whose address can't be changed:
int *const x;

// And this can't have its address changed, and can only be read from:
int const *const x;

Now I'm going to digress a bit for a second.  There is a concept of a side effect in programming, for the sake of simplicity let's call them "expressions that do things".  Side effects are:

1. Reading from/writing to memory in a way that can change if the same line of code is run twice; and
2. Doing some form of input/output.

The thing about const is that what const does is says that after the declaration you can't change the variables in ways your variable declaration wouldn't allow, but you can (and always, always should) initialize it, even to an expression with side effects.  That is:

int const x = getNumberFromFile();

Is perfectly valid.

Now you might be asking "does this exist at runtime?" It doesn't.  It has two main uses.

First, it prevents you making mistakes.  There is a practice called const correctness, which basically means that you put const everywhere it makes sense to.  If you do that, the compiler will catch you if you try to change something that shouldn't be changed, i.e. a global constant variable, to name one common use.  Additionally, when you get into structs, you will discover that const lets you make some fields of a struct read-only.

Second, when you use it in function parameters, it provides documentation and a weak guarantee.

The documentation tells the user in what ways the function will change its arguments, which isn't super important for non-pointers, but for pointers there's a big difference between foo(int *p) and foo(int const *p) because the latter says "I swear I will not touch the int you passed me".  Ints are actually smaller than pointers so that doesn't make sense, but for large structs this is important since if you don't pass a pointer you might be copying hundreds of bytes, and the const on the pointer says that the caller should just go ahead.

The weak guarantee is that the function taking the const parameter can't make changes, at the compiler level.  This is weak because you can actually get around const if you want, and there  are even some good (but advanced and very raree) times you should do that.  This doesn't actually just extend to expressions, it also makes you incapable of passing those values to other functions that might want to make changes.  A really big one here is free, which takes a void* instead of a const void*, that is to say a non-constant pointer to anything as opposed to a constant pointer to anything.  Why? Because the change that free makes is it takes all the bytes pointed to by the pointer and makes them become invalid bytes.

So some places you'd want to use const: on global variables that hold values that don't change for starters.  If you compute sizes of objects, that's another good place: presumably the size isn't going to change.  It's hard to give examples because const correctness is a sort of I know it when I see it thing.

But beyond being there for you (and one day, if not now, you will understand that it's actually your friend even if it is annoying) it mostly doesn't do anything.  Poor C compilers can use it as a hint for optimization, but things like GCC are now advanced enough that they don't bother looking at it to my knowledge.

My Blog
Twitter: @ajhicks1992

2020-03-20 23:33:47 (edited by amerikranian 2020-03-20 23:38:03)

Just to clarify, we still use the pointer and the i, we just dereference the pointer by adding i to it instead of using a subscript. Correct?
For example: assuming that we have numbers and the array pointer from your 41st post.

for (int i = 0; i < a_length; i ++) {
    int item = *(p + i);
    //Do stuff
}

2020-03-20 23:44:17

Nope!  You can subscript pointers.  It works fine.  The following two expressions are exactly 100% equivalent in every way:

p[i]
*(p+i)

and most coders use the former for clarity.

Yes, that does mean these are equivalent too:

*p
p[0]

And disturbingly, so are these (never ever do this):

i[p]
*(i+p)

I don't know why the last is allowed.  Probably something something something history something X86 assembler syntax.

The thing you can't do is subscript a pointer twice, i.e. treat it as a 2 dimensional array.  The first subscript of an int pointer is an int, which can't be subscripted further.  Yes, pointers to pointers do in fact exist, and you will get there shortly.  Indeed I have once or twice needed a pointer to a pointer to a pointer to foo.  If you have more than one level of pointer the subscripts always remove one level, so a int **x can be subscripted twice, an int ***x can be subscripted 3 times.  It is okay if this particular paragraph gives you a headache for a while; lots of people have trouble with pointers to other pointers.

My Blog
Twitter: @ajhicks1992

2020-03-21 00:09:10

@44, I had no idea you could do that last one.

"On two occasions I have been asked [by members of Parliament!]: 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out ?' I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question."    — Charles Babbage.
My Github

2020-03-21 00:21:26 (edited by amerikranian 2020-03-21 00:22:33)

Ah, I understand now.  Thank you so much for your help. I don’t know where I would be without all of your explanations.
I am going to proceed to quickly forget the last example. Why would anybody use that.

2020-03-21 00:43:56

@45 and @46
It is one of those C "fun facts" that people use in obfuscation contests.  I feel like I saw exactly one use of it somewhere that was justified buried in huge levels of preprocessor macros but I can't remember specifics.  I am not exactly happier for having known that this terrible, terrible horror exists.

The rest of this probably isn't news to you, Ethin, but for amerikranian:

C goes back a long, long time.  So long that it was compiling on machines with under a megabyte of ram, as in literally C compilers could run on such computers.  C isn't a popular language because it's great, it's popular because it's old, and reasonably low-level (though, not as low-level as you'd think these days--Intel for instance has a lot of magic between C and the chip).

It didn't get popular because it had a great standard library; it didn't get popular because it's got amazing abstractions; it didn't even get popular, originally, because it's fast.  it got popular because back in those days, there wasn't just one or two major architectures.  There were at least 20 or 30 of them all competing with each other, and each with their own dedicated assembly syntax.  C compilers back then didn't optimize, or even check your code very much, they just one-for-oned your code directly to assembly one line at a time.  This is the reason things like headers exist--it was too inefficient to look ahead, since back then your program on disk would be many, many times bigger than the memory in the machine, and it had to load it very small chunks at a time.

And, you can still implement a C compiler that way.  In fact it's a not at all uncommon college homework assignment, to make a thing that one-for-ones C to assembly, line by line.  But the big thing that C did, is it let people on all the different weird architectures start sharing code with each other.  But back then, you still had to jump into assembly sometimes and stuff, nd the people writing it had to know assembly too, because back then we just didn't have higher level stuff.

So, my guess as to why this exists is this.  For efficiency, some instructions in assembly treat a processor register like a pointer, but it is inefficient to make you add with a separate instruction all the time, so eventually the argument to the instruction was an address and a constant value to add to that address.  And what they landed on, for X86, looked like this:

movl    8(%ebp), $123

Will figure out whatever %ebp points to, add 8 to it, and store 5 there.  In assembly it has to be written this way, you can't put the number after.  So my guess is somenoe just went "you know, C should let me do this too", and somewhere in the 70s or 80s that got added, and now it's with us forever because C never, ever breaks compatibility without literally 10 years notice.

My Blog
Twitter: @ajhicks1992

2020-03-21 02:53:23

@47, technically there still are 20-30 architectures out there; the majority aren't really competing thoug. For instance PowerPC is still actively developed (I know of a fully open-source computer, with a fully auditable computing domain, for instance). RISC-V is another one, though RISC-V is more of an academic architecture (though I wish it got more widespread use, it looks to be pretty nice).

"On two occasions I have been asked [by members of Parliament!]: 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out ?' I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question."    — Charles Babbage.
My Github

2020-03-21 04:46:00

@48
O sure, but for the sake of most practical discussion you can say X86 and Arm and you've covered a vast majority of anything anyone might encounter without going into specialized domains.  But ye olden days...well...not so much.  It's a different ethos.  I'm not trying to sweep other architectures under the rug.

My Blog
Twitter: @ajhicks1992

2020-03-21 21:43:11 (edited by amerikranian 2020-03-21 21:44:55)

So, I've been learning all about how pointers are more efficient and faster and better and... you get the point. My question is now... when would I not use a pointer? This is pretty overkill, but it demonstrates what I mean.

int x = 5, * pX = &x;
int y = 23, * pY = &y;
double delta = 5.4894, * pDelta = &delta;
//...

Obviously, I'm not going to create a pointer per variable, but my question still stands: When would I not use a pointer? Googling some common usages yielded arrays and memory management which again is fine, but is that the only thing pointers should be used for?
Also, something I just thought of: Why are pointers more efficient than arrays when doing something like the following?

void test(int * p, int x) {
    printf("%d", p[x]);
}