2020-03-21 22:46:33

You're confusing two kinds of efficiency.  There is efficiency as in "this code is easy to maintain" or "this code isn't twice as long as it needs to be", and then there is efficiency as in "This code took 2 seconds to run"

And, with that said, pointers are never faster with regard to time efficiency (hereafter just efficiency).  The compiler gets to make a lot of assumptions about non-pointer variable access that aren't necessarily true of pointers, though in the modern environment the compiler will usually be able to see through them and will often produce the exact same assembly irregardless.

Pointers are also often bigger than the thing they point to.  On many 64-bit systems, an int is 4 bytes, but a pointer is always 8.  So if you make a pointer to an int variable, you're using 12 bytes total instead of just 4 unless the compiler decides to eliminate it (and yes, your compiler will remove variables from your code once you turn on optimization, among other things).

Arrays are like constant pointers, which I think is another source of your confusion.  They're not exactly the same syntactically, but all an array really does is asks the compiler to please allocate some space at compile time instead of relying on the OS.  Indeed, arrays decay to pointers, that is to say that the following works:

int array[] = {1, 2, 3};

void foo(int *x) {
    // stuff with x.
}

foo(array);

Where the last line could also be written foo(&array[0]).

The other kind of efficiency, more properly called code clarity or good code (as in the sentence "This is good code") is about how easy your code is to understand by other programmers, and how easy it is to maintain when you come back months later.  This ties back into the const discussion above, since const is about making better code.  And, in that aspect, using pointers unnecessarily is always bad, because (like with the do loop way at the beginning of this thread) it will cause the reader to try to find the reason.

So, let's talk about a concrete use of pointers.  A long time ago, I did Libaudioverse, which was one of my attempts at 3D audio.  here is the function from its header for making a new BufferNode:

Lav_PUBLIC_FUNCTION LavError Lav_createBufferNode(LavHandle serverHandle, LavHandle* destination);

So let's walk through this (I'm not showing the implementation because that's C++, though the header is C):

First is Lav_PUBLIC_FUNCTION, which can be ignored: it's to do with making functions visible in DLLs.

Then we have LavError, which is an enum, a sort of int where you get to name the values of the int.  You'll get to them eventually, but for now it's fine to say it's an int.  It returns what went wrong if anything, or 0 if everything was fine.

Then of course, the name of the function, and a handle to a server, which you created previously.  Then, apropos to this discussion, LavHandle *destination, which is where the function puts the actual handle if it made one (and if it didn't, it just never writes the pointer).

This is idiomatic use of C pointers for functions which both do something and have errors: the error is what gets returned.  This is good because you can do:

LavError error;
LavHandle bn;
error = Lav_createBufferNode(server, &bn);
if(error != 0) {
// handle error, i.e.
return;
}
// do stuff with handle.

And to see why, let's examine our alternatives.  First, we could have a struct:

struct LavBufferNodereturnValue {
LavError error;
LavHandle bufferNode;
};

Which works fine except that you need a different one for every single function, and every consumer of the function has to figure out what the return type is.  Now you could say that there's a struct for every unique return value, i.e.:

struct HandleError {
Lavhandle handle;
LavError error;
};

Which is fine, except that you will need a version of this for every different group of parameters that a function might want to return.  Additionally, it's big, which sometimes matters, and will be copied, which also sometimes matters.  Plus, every library will do this differently.  Or, you use what's called an out parameter, which is what I demonstrated above.

There are many other places where pointers help. Some examples:

A situation in which you need to read and/or write from one of a bunch of different variables all of the same type, but you don't know which one at runtime.  This comes up frequently in data structures like linked lists and trees.

A situation in which a function needs to modify a large variable, say a 1 kilobyte struct with 100 different fields in it.  These exist and are actually pretty common when you do bigger programs.  For example Libaudioverse's server is a C++ class (but for the purposes of our discussion a struct) which contains something like 4 or 5 other big objects each for a different subsystem which contain which contain and so on for quite a way.  If you pass this around as a value, it gets copied every time, and if the functions want to modify it they'd have to return a copy back and copy again on the way out.

Indeed, this second case is how you do classes in C:

// UserDatabase Type comes from somewhere, we don't care right now.
UserDatabaseError MyObject_CreateUser(UserDatabase *o, char *username, char *password);
UserDatabaseErrorMyObject_deleteUser(UserDatabase *o, char *username);
// and so on

And the third most common place pointers come up is function pointers, which are pointers to functions.  You will learn about them shortly and have probably seen references to them already, but the idea is this (and I'm using a real example from sqlite):

Say I'm making a database library that's going to store data in the filesystem, but the filesystem could be anything: a disk, a cloud storage offering, a segment of memory, whatever.  And maybe the implementation of it doesn't even come from C and the app wanting to use the library has 5 different filesystems it wants to work with simultaneously.

We solve this with pointers to functions: a filesystem only has a few operations, read/write/create/delete really, so we make the user provide function pointers to functions to do that and default to our own if they use NULL instead.

This is a lot, but let me just close with this.  Don't try to figure out what is faster in terms of time.  If you want some idea what compilers are like, I actually worked on one some years ago, and I did a writeup on what exactly I did here.  When it comes to compilers I'm usually the least informed person in the room, so imagine hundreds to thousands of people 4 to 10 times smarter and more experienced than me spending the last 20 years working on your compiler to make it magically make decisions that make your code faster and smaller.  Rule 1 of "is this faster" is to not guess but actually measure it and rule 1 of measuring it is that, without a lot of experience in how to do such measurements, you're probably measuring it in an unreliable fashion anyway.

My Blog
Twitter: @ajhicks1992

2020-03-21 23:27:27

Thank you again for the informative response. I hope you're not getting annoyed with me... I typically have an easy time picking up the syntax and the base rules and need some help making the pieces click in place.

2020-03-21 23:35:04

I'm not, though I'm curious what resource you're using.  If anything this thread is suddenly making me realize that it might be worth me revisiting writing a book on programming.  I've tried a few times before, but it was a long, long time ago before I was anywhere near as experienced as I am now.  But this is making me realize that I can't think of any resources that talk about philosophy, and everything I can think of does arrays before pointers too, which as you're probably discovering is entirely backward from how you actually have to understand it if you want to do anything at all.

My Blog
Twitter: @ajhicks1992

2020-03-21 23:44:00

I use C in a Nutshell: The Definitive Reference (9781491904756) personally. Though its reference material, it does cover all of this (though it doesn't cover the more obscure syntax -- it leaves that up to the standard).

"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-22 00:04:40 (edited by amerikranian 2020-03-22 00:07:40)

I used Absolute Beginner's guide to C, more to get started than anything. The problem I found with the book is that it only mentioned things briefly (only one section was about pointers and with very few examples) and more focused on connecting basics to the real world, which is great but not exactly helpful for when I already have the concept of looping and functions. I also have purchased a course on Udemy (the name escapes me right now though I can provide the link to it here in a bit if you wish) but the instructor has a multitude of issues (extremely slow speech, long awkward pauses while searching for words, reiteration of the same, typically useless, expected result... I could go on). I am not really disappointed with the former seeing as it was called "Absolute Beginner's guide," but the latter makes me rather upset because it was one of the bestseller courses and came from the same group of instructors from who I purchased before and was extremely satisfied with. It was on sale, though, so that's a plus. Only costed $12.99 from it's typical price, $149 or so.
My problem with both the book and the course is that there is not enough material given to cement the concepts being taught. I'm not talking about syntax, that comes to me as naturally as breathing, I'm talking more about the reasons behind why things work (pretty much all my troubles on this thread).
My school unfortunately does not offer a course in C, only in Java, and I have already went through it. To be honest, based on my experiences, even if my school did offer the course I wouldn't have taken it. By the end of the year in Java we only got to basic file processing. No talking about inheritance, no mention of abstract classes... Supremely disappointing. Oh well. That's the joys of high school.
I don't want to pick up reference materials primarily because I don't think they will help me at my current state. What I need is a combination between the two: A demonstration and an explanation of why it works and what goes on behind the scenes. In an ideal world I could also have the ability to ask questions as I think of them, but... well. That wouldn't happen til college when everyone else who takes the class will begin caring about their time.

2020-03-22 00:33:22

@55
To start with your last point first: forget about asking questions in college unless it's a good one; the professor will have the answers but almost no one else will give a fuck and is just doing it to get the money out the other side.  Obviously that's not true of MIT or whatever, but bumblefuck community college certainly will be that way.

But I think your problem is you're looking at resources for programmer newbies that teach C, not resources that teach C to programmers.  There is a difference.  The former will connect things to the real world at an entirely different level than the latter, since for the former it's also assumed that you couldn't write a tiny board game thing or something at this point.

So, some suggestions.  First, a google for "C for Python programmers" turned up this as the third result or so.  Replace Python with java or C# or whatever else and you can probably find tons of resources aimed at people trying to make that jump.

If you don't mind learning some Rust, this is their official tutorial for people (since Rust is new enough to have such a thing).  I bring this up because Rust's official tutorial is aimed at people who can already program in non-systems languages and written from the perspective of "This is Rust. Here is what a stack is. Here is why Rust does this this way and an example of why if we didn't you'd crash".  Etc.  The big thing that Rust has that C doesn't is lifetimes, which is a sort of formalism on top of pointers that gets rid of a class of issue in C where you have pointers pointing at invalid memory, and admittedly Rust has a lot more going on than C, but their resources are very good for learning.

I would never pay for a learning a programming language resource unless it also comes with some sort of accreditation or there were some other outstanding reason to bother.  There are way too many free things out there, and unlike sighted people the free ones tend to be more accessible anyhow.  If this was I need to learn assembly for weird architecture that only 5 people know, or advanced mega-senior level concurrency algorithms with a discourse of common microprocessor microcode implementations or something with lots of big words that I threw together for this example, okay, maybe. Otherwise...no.

My Blog
Twitter: @ajhicks1992

2020-03-22 00:52:44 (edited by amerikranian 2020-03-22 00:54:07)

See, I don't mind. Reason why I typically invest in books or courses is because of the logic of "They know how to teach this in the most succinct manner." Obviously, not true, but hey. At least they have some sort of a plan where as I may end up floating place to place looking for answers and or tutorials about a concept that I felt I still didn't understand after reading my current resource. I have no issue with it, but a part of my brain hates "wasting time" (do note the quotation marks) on research, at least when it comes to trying to find the right resource. When it comes to learning, I tend to be cautious... perhaps too much so.
Point noted about college, too. A pity, but hey, what can you do.
I may learn rust in the future, we'll see. I do want to get to a point where I feel semi-comfortable with C before learning anything else, though.

2020-03-22 01:02:18

@57
No. No. You misunderstand.  I'm not saying don't use a book. I'm saying don't pay for a book.

The people who write free books on the internet for this stuff are equally qualified to the people who write non-free books not on the internet for this stuff, except that the latter bothered to try to get a publisher interested in selling it and the former did it as a labor of love with only the hope that someone might find it useful as payment. Also a lot of universities put up free textbooks for this stuff too.

I would point you at specific things but I did this 10 years ago and frankly this thread is managing to make me feel old.

My Blog
Twitter: @ajhicks1992

2020-03-22 01:37:17

Ah, my bad. Sorry for making you feel old, LOL.

2020-03-22 01:49:17

I did point to a good book in 54. Its what I sometimes use personally, though cppreference.com is definitely the best resource on C and C++.

"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-22 01:50:34

@59
Haha lol well.  At this point, this entire site makes me feel old.  My running theory as to why we don't have many audiogames that are amazingly amazing is that those of us who get jobs leave; and I'd have been among that number if Synthizer hadn't dragged me back in.

My Blog
Twitter: @ajhicks1992

2020-03-28 01:45:26 (edited by amerikranian 2020-03-28 01:46:58)

So, after taking a break from C for a bit, I decided to do some advent of code challenges.
No, I'm not going to ask for help on solving them (that would defeat the point of the exercises). My problem comes with reading data from files.
First, I'll give you the file contents.

1,0,0,3,1,1,2,3,1,3,4,3,1,5,0,3,2,1,9,19,1,19,5,23,1,13,23,27...

and so on. Cool. You don't need to know what the numbers should be doing, all you need to know is that there are commas separating each number from one another.
Now comes my code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    FILE * fptr = fopen("input.txt", "r");
    fseek(fptr, 0L, SEEK_END);
    int totalRead = ftell(fptr);
    rewind(fptr);
    char * buffer = (char * ) malloc(totalRead);
    int actualRead = fread(buffer, sizeof(char), totalRead, fptr);
    fclose(fptr);
    //Array for holding all those numbers
    int nums[500];
    int counter = 0;
    //The part that doesn't work
    char * token = strtok(buffer, ",");
    while (token != NULL) {
        //Convert token to integer, not properly handled because there will not be any failure of a sort
        nums[counter] = atoi(token);
        token = strtok(NULL, ",");
    }
    //Debug purposes
    printf("%d", counter);
    free(buffer);
    return 0;
}

Run this bit of code and you'll see that the counter stays at 0. Why? All the tutorials I found for strtok have used the same code for splitting strings into chunks.

2020-03-28 04:40:28

You never increment counter.  Put counter ++; at the bottom of your loop.

Additionally, you need to allocate one extra byte in the buffer you're reading data into.  Then after reading data into it, set buffer[actualData+1] to '\0', the null character which terminates strings. Otherwise this code will randomly crash because strtok won't know to stop scanning.  I think the reason that it's not is that you're getting lucky.

My Blog
Twitter: @ajhicks1992

2020-03-28 05:01:37

Couldn't you just use

fscanf(fptr, "%d,", &dummy);

(where &dummy is any variable) instead of going through the hell that is strtok for something like this?

"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-28 05:22:27 (edited by amerikranian 2020-03-28 05:24:02)

I suppose you could.  I will probably do that in the future.
I feel so stupid for not implementing the counter. I am sorry.

2020-03-28 05:38:46

@65, would you be surprised that even the best programmers make small mistakes like that? Sure, they're really good at what they do, but when you've just started writing a program -- or, even, in professional projects, small mistakes are made that usually have massive impact. That's why we have code reviews in everything from git commits to (most?) professional contracts. No reason to feel stupid. Shit happens, as they say.

"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-05-19 21:43:20 (edited by amerikranian 2020-05-19 21:44:26)

Sorry to revive this, but I have an interesting dilemma and I'm not sure as to what to even search for.
I converted a 2d array to a 1d array. My formula for converting an index to x / y coordinate is as follows:
x = index % width
y = index / width
Assume that y is an int and thus trims off any floatingpoints.
When I printed my coordinates with the following loop, I was surprised as to the result.

    for (int i = 0; i < x * y; i ++) {
        int tempx = i % x;
        int tempy = i / x;
        if (tempx == 0 && tempy != 0)
        printf("\n");
        printf("%d, %d,", tempx, tempy);
    }

The output?
0, 0,1, 0,2, 0,3, 0,
0, 1,1, 1,2, 1,3, 1,
0, 2,1, 2,2, 2,3, 2,
0, 3,1, 3,2, 3,3, 3,
and so on.
As you can see, my x values are increasing, which makes me wonder if I screwed something up. You see, typing in a 2d list in python and looping through it yields me an increases in why first, i.e,
(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 0)...
So... where did I go wrong here? My allocation for the 1d array is not an issue here, I don't think so anyways, but I'll paste it in here just in case it's at fault.

int *arr = (int *)malloc(x * y * sizeof(int));
//do stuff
free(arr);

So, yeah. Tips would be welcome.

2020-05-19 22:11:53

Firstly, whether x or y increases as the outer dimension is mostly a convention.  You can do it either way.  If you do the indexing as x first, then it's actually x*height+y to get to a linear index, so your inverse formulas (by algebra) are:

x = i / height;
y = i % height;

If you want an intuitive way to think about this, each increase in x moves you by height elements in memory, and 0 <= y < height, so y is the remainder after you move by x*height.

You're only showing us the output of the Python code, so I'm not sure what you did in Python, but in Python a list of lists is actually a real, tangible thing, so it's not surprising they're different.  In C, 2-dimensional array types are just telling the compiler you'd please like it to do some multiplication for you, and are actually the same in memory as 1-dimensional arrays.

My Blog
Twitter: @ajhicks1992

2020-05-19 22:14:44

Also just to add in one more thing. In graphics programming, it is actually usually y first so that the rows of pixels are adjacent in memory, so many resources you find for game programming may be doing:

array[y][x];

But in general this is a good lesson about how the layout of things in memory and the abstraction you use to interact with them are different, in that if you hide this behind a function the memory layout isn't something the external world can see, and so that's what most game programming stuff that needs to get weird about arrays ends up doing.

My Blog
Twitter: @ajhicks1992

2020-05-20 01:12:52

Ah. I see. The row by row scan actually makes sense now.
As for Python code, my bad. Something like this should do.

lst = [[a for a in range(5)]for a in range(5)]
for i in range(len(lst)):
    for i2 in range(len(lst[i])):
        print(lst[i][i2])

Produces the output of 0, 1, 2, 3, 4, AKA the elements of the list before moving on. A better example could be just to change

lst[i][i2]

to i, i2. You would see i2 rising from zero, to one, to two, and so on until it goes back down to zero before i increases. That is how I expected the 1d representation to behave, but I now understand why the two output different elements.

2020-05-20 02:07:10

@70
I'm not sure what you're saying exactly, but in general, given:

array[a][b][c][d][[e][f]...[z];

Indexes further to the right increase the fastest.  In a 2d array the second index going up by 1 moves you forward 1 element. In a 3d array the 3rd going up by 1 moves you one element  forward.  If you want to match what the C compiler is doing and x is first, it's x*height+y because y "increases the fastest" as you move up in memory (I did not choose the term increases the fastest; this is just what everyone uses. But if you think about it it will make some sense).  If it's y first, then it's y*width+x.  The rightmost index is always added with no multiplier, and as you move left you multiply by the size of what's to the right in the type.

My Blog
Twitter: @ajhicks1992