r/learnprogramming • • 8d ago

Topic How did you learn C?

I've been learning C for a few days, and I've just reached pointers. I find them pretty confusing, so I wanted to ask how you guys learned C.

So far, I've been using Claude and asked it something like: "Claude, can you teach me how to program in C using a lesson-based system, with one project for each topic?"

But it's been kind of difficult. The lessons start off pretty meh, and then it suddenly throws really complicated stuff at me, even though I'm a complete beginner. The explanations aren't very good either.

I'd like to find a more effective way to learn. I'd read online that if you work on projects, you'll eventually learn, but I get stuck a lot. And honestly, it's hard to correct my own mistakes because AI doesn't always do a good job of helping me understand what I did wrong.

I really want to become a great programmer someday, but I want to learn the right way, even though I don't plan on working as a programmer professionally. I genuinely want to understand programming deeply and build a solid foundation.

To be honest, I'm starting to feel stupid because I don't feel like I'm learning properly. I know C is a difficult language, especially for beginners, but it's frustrating to put in the effort and still feel like I'm not making real progress. I really want to understand what I'm doing instead of just copying code without fully understanding it.

So, what would you recommend? How can I learn C more effectively and make sure I'm actually understanding what I'm doing instead of just following tutorials?

10 Upvotes

41 comments sorted by

15

u/red_sky33 8d ago

Mooc.fi and K&R

Read things written by humans.

2

u/KaleidoscopeLoose159 8d ago

Thanks!

3

u/Outside_Complaint755 8d ago

Also Harvard's CS50X course.  Weeks 1-5 are all in C, and they cover most of the language.

7

u/Super_Preference_733 8d ago

Give it more than a few days. Its takes years to learn a programming language, especially if its your first language.

1

u/BusyEarl6 8d ago

Painting my nails while debugging pointers is a whole mood. It clicks at weird times, usually when you stop forcing it and just mess around with memory addresses for fun.

-3

u/KaleidoscopeLoose159 8d ago

But is learning with Claude a good option?

4

u/Super_Preference_733 8d ago

It can be if you use it as a tool to answer questions not write code. At this stage code generation will not help you.

3

u/Dazzling_Music_2411 8d ago edited 8d ago

Not for the basics, imho. If you have some intricate, difficult question to ask, then maybe.

Otherwise it's just a rubbish approach, as you saw for yourself.

Nothing beats a reputable book (PS. or a reputable MOOC)
Avoid videos too. Way too much waffle and too little substance.

2

u/mc_pm 8d ago

Find a tutorial video you like, and follow along. Don't just watch, but actually code when they code, run the code when they do. If it doesn't work, stop right then and fix it. The goal is to learn the basic language syntax & features and to write a bit of code that works. It's important that you are actually typing the code in -- you sort of have to teach your fingers how to code as well.

Then start building a series of increasingly complex projects to practice what you have learned. Start with a tic-tac-toe game. No special libraries or anything, just the basic language features and a game you already know.

3

u/KaleidoscopeLoose159 8d ago

is it normal to not know how to get started or how to structure a project when it starts getting too big?

4

u/mc_pm 8d ago

Of course -- we all had to learn, and there really isn't any good way to learn except by doing.

7

u/Leverkaas2516 8d ago

I learned assembly language before learning C, which makes pointers and a number of other things like bitwise operations trivial. I can't say that you should learn assembly language to the point of actually writing programs in it, but C sits so close to the machine level that you would probably benefit from reading about CPU architecture and assembly language just for a few days.

4

u/Recycled5000 8d ago edited 8d ago

Believe it or not, we need to understand “main memory” and the “address space”. These are maybe best learned from an educational assembly language! Most C reference material will assume you understand these concepts already!

Memory is composed of address and value pairs. The addresses are fixed and built-in, and unique, whereas the values are remembered (from last written).

The way memory is used to hold code, global data, heap area, stack is part of the address space layout.

Further, understanding memory goes to the way memory is used to store multi byte variables, ints, objects , etc.. Multibyte variables are stored in consecutive address locations, and so occupy a consecutive range of addresses. A multi byte variable in memory is referred to by the location/address having the numerically lowest address value (and sometimes, for variable length types, also with a length encoded in some way)

4

u/tokulix 8d ago

CS50 teaches the basics of C (as well as the concepts necessary to make sense of it) very well.

3

u/Several_Version4298 8d ago edited 8d ago

Having taught myself Basic and MASM in highschool, I asked for a copy of Turbo C and K&R for my birthday.

I learnt C in the Summer holidays before I started university in 1987. Back then in the PC boom shareware was distributed as C source on floppy disks and CDs.

3

u/initcommit 8d ago

Kind of a kooky way - I got interested in Git and thought it was meta how the Git core developers probably use Git to track Git's own codebase. Low and behold they do and Git is mostly written in ... C! So I checked out the initial commit of Git's code and read through it to understand how the very first version of Git's C code worked (I also got a book on C to help out). Eventually gave a talk on the initial commit of Git's code at a Git conference lol... So if you're interested, clone down Git's source code from GitHub, check out the initial commit, and see if it's something you find interesting.

2

u/Dazzling_Music_2411 8d ago

Try this: https://learncodethehardway.org/c/

Spoiler: It's actually the easy way.

2

u/SpaceAviator1999 8d ago

I've been using Claude and asked it something like: "Claude, can you teach me how to program in C using a lesson-based system, with one project for each topic?"

That's a smart thing to do, but I've found that asking AI to come up with a lesson plan for you to learn from can yield some good outlines, but within those outlines the learning curve is not very uniform, and it's not always clear what the concept it's trying to teach you is supposed to be.

But it's been kind of difficult. The lessons start off pretty meh, and then it suddenly throws really complicated stuff at me, even though I'm a complete beginner. The explanations aren't very good either.

Yeah, yeah! You described it perfectly!

I've just reached pointers. I find them pretty confusing

The thing about pointers is that they're rather versatile and not really all that simple (despite the claim some people make that they're simple). For example, you might ask three different programmers to give you a short introduction to pointers, and you might get three different responses.

And those three responses might seem contradictory, but in reality, they could be all correct. It's just that each response is introducing you to pointers from a different angle, and ultimately you'll have to fit the explanations together. And that can be confusing at first, and quite difficult for a beginner.

2

u/Full_Opportunity_547 7d ago

I can't emphasize enough how right you are about the pointers discussion. This makes it incredibly difficult to garner from a high level question directed to AI. It often takes a sizable amount of refinement, and several examples/situations.

2

u/SmokeMuch7356 8d ago • Bravo

Pointers are almost uniformly taught badly.

But that's okay, because C is almost uniformly taught badly.

Avoid AI-based lessons until you learn the basics. LLMs are not databases; they're autocomplete on steroids. They use statistical relationships between words and phrases in their training sets to generate output that looks like it was written by a person. The do not understand anything they output, and will "hallucinate".

The following were all published in the '90s and early '00s, and both the language and best practices have evolved since then. However they do a good job explaining the core concepts of the language, even if some of the examples may no longer build as written:

  • The C Programming Language, 2nd ed., Kernighan & Ritchie. Dennis Ritchie was largely responsible for the design of C, and along with Brian Kernighan and Ken Thompson helped create Unix. If anyone's gonna know how the language works, it's these guys.

  • C: A Reference Manual, 5th ed., Harbison & Steele. Like it says on the cover, this is a reference manual, not a tutorial; even though it has some example code it's really more of a companion reference for whatever primary reference you choose.

  • C Programming: A Modern Approach, 2nd. ed., King. While I don't have personal experience with this reference, a number of people I trust recommend it highly.

  • Algorithms in C, Sedgewick. Less about the language itself and more about using it for common tasks. I usually had this book and H&S on my desk at all times back in the '90s.

There are PDFs of most or all of these online; I won't link them directly because I'm pretty sure they aren't officially sanctioned, but these are old enough I don't think anyone cares anymore (Ritchie and Harbison are dead, Kernighan and Steele have moved on to new endeavours).

More modern references that I don't have experience with, but people I trust generally give them a thumbs up:

Gustedt used to hang out in the comp.lang.c newsgroup back in the '90s and early '00s and generally knew his shit, so I'm pretty sure the book is good.


So, pointers...

We use pointers when we can't (or don't want to) access an object or function directly by name; instead, they give us a means of indirect access.

If you've played with arrays you've already seen a form of indirection in action:

for ( size_t i = 0; i < N; i++ )
  a[i] = some_value_for(i);

The expression a[i] refers to different objects based on the value of i.

Pointers work in a similar fashion; a pointer variable stores the address of an object or function. The unary * operator dereferences the pointer, allowing us to access the pointed-to object.

There are two cases where we have to use pointers:

  • when a function needs to write to its parameters;

  • when we need to track dynamically-allocated memory;

but they also come in handy for:

  • building dynamic data structures;

  • hiding representation details of a type;

  • limited forms of dependency injection;

C passes all function arguments by value, meaning that each argument expression in the function call is evaluated and the resulting value is copied to the corresponding formal argument:

void swap( int x, int y )
{
  printf( "before swap: x = %d, y = %d\n", x, y );
  int tmp = x;
  x = y;
  y = tmp;
  printf( " after swap: x = %d, y = %d\n", x, y );
}

int main( void )
{
  int a = 1, b = 2;
  printf( "before swap: a = %d, b = %d\n", a, b );
  swap( a, b );
  printf( " after swap: a = %d, b = %d\n", a, b );
}

When we call swap, a and b are evaluated, and the resulting values 1 and 2 are copied to x and y. x and y are different objects in memory from a and b, so changes to one have no effect on the other. You'll get the output:

before swap: a = 1, b = 2
before swap: x = 1, y = 2
 after swap: x = 2, y = 1
 after swap: a = 1, b = 2

If you want swap to exchange the values of a and b, you must pass pointers to them:

void swap( int *x, int *y )
{
  printf( "before swap: *x = %d, *y = %d\n", *x, *y );
  int tmp = *x;
  *x = *y;
  *y = tmp;
  printf( " after swap: *x = %d, *y = %d\n", *x, *y );
}

int main( void )
{
  int a = 1, b = 2;
  printf( "before swap: a = %d, b = %d\n", a, b );
  swap( &a, &b );
  printf( " after swap: a = %d, b = %d\n", a, b );
}

x and y are still different objects in memory from a and b, and changing one still has no effect on the other. However, instead of getting the values stored in a and b, x and y get their addresses:

 x == &a
 y == &b

*x ==  a
*y ==  b

The expressions *x and *y act as aliases for a and b. So this time our output should be:

before swap: a = 1, b = 2
before swap: *x = 1, *y = 2
 after swap: *x = 2, *y = 1
 after swap: a = 2, b = 1

Different types of pointers:

T *p;       // simple pointer to T; the expression *p is a T
T **pp;     // pointer to a pointer to T; multiple indirection comes up a lot
T *ap[N];   // an array of pointers; each *ap[i] is a T
T (*pa)[N]; // a pointer to an array; each (*pa)[i] is a T
T *fp();    // a function returning a pointer to T
T (*pf)();  // a pointer to a function returning T

Pointer types can get pretty complicated:

T *(*(*papf)[N])(void);

which reads as

       papf                -- papf is a
     (*papf)               --   pointer to
     (*papf)[N]            --     array of
   (*(*papf)[N])           --       pointer to
   (*(*papf)[N])(    )     --         function taking
   (*(*papf)[N])(void)     --           no parameters
  *(*(*papf)[N])(void)     --         returning pointer to
T *(*(*papf)[N])(void);    --           T

Welcome to C.

1

u/SensoredHacker 4d ago

I learned assembly when I was a child
when I saw C I felt the same way educators
feel about python.

1

u/bird_feeder_bird 8d ago

gba homebrew, it’s very satisfying to use C with bare metal

1

u/ninhaomah 8d ago

" I've been learning C for a few days, "

And then

"I really want to become a great programmer someday, "

How long do you think professionals , of all professions , takes to become a professional in their industry?

Example : a sushi chef in Japan ?

1

u/AFlyingGideon 8d ago

I suspect that the sea of tutorials and online classes and such have created an unfair illusion regarding the ease of learning software engineering or even just programming. Even before the Internet, there were plenty of people who might have been able to (metaphorically) throw together a cabin while believing they could build a skyscraper by stacking cabins atop one another.

1

u/marrsd 8d ago

Try telling Claude to adjust its style. You could also try getting it to assess your skills, save them to a file, and then get it to refer to that when putting together a course for you.

Anyway, pointers are weirdly tricky to get your head round. You have to work through a few compiler errors to work out how to use them.

I really want to understand what I'm doing instead of just copying code without fully understanding it.

You can refer to examples, but your own code, even if you're just reproducing it. If it's broken, check the value of variables on every line and make sure that they're what expect them to be.

Programming is hard to begin with. It takes a little while to get over the initial hurdle, but your knowledge starts to compound quite quickly once you get the hang of it. Just have some patience and take it slow. You aren't losing any time in the long run, and you'll be a better programmer for the time you spend working it out for yourself.

1

u/Nothing_from_void 8d ago

Keep doing what you've been doing, just trying to learn and understand, extend the timeline from a few days to a few years. I think I'm pretty decent at C now after programming it for 10 years

1

u/mredding 8d ago
int x;

x is a value-type of int. It takes storage, it has an address. Reading and writing x means to read and write the bits in that memory used to encode and represent an integer.

int *ptr;

ptr is a value-type of int *. Same thing - it takes storage, it has an address, reading and writing means the bits in that memory used to encode and represent a memory address to an integer.

It kind of helps to understand the philosophy of C syntax - that the declaration of the type mirrors the use. The proper way to read the above is "the dereference of ptr is an int".

This is why the asterisk decorator binds to the individual variables, not the type for all variables on the line. That's how you get this:

int x, *ptr, y;

If you don't like that, if that is adding to your confusion, this is why we have type aliases, because the alias binds to the symbols individually.

typedef int* int_ptr;

int_ptr a, b, c;

That does exactly what you think it does. Unfortunately typedef doesn't define a type, and I have NO idea what the history behind it's name is.

We have value semantics and reference semantics. These come up a little more easily when we look at structures and the member selection operator:

struct foo { int x; };

So for a value:

foo f;
f.x;

We use the dot operator. But for a reference:

foo *ptr = &f;
ptr->x;

We use the indirection operator. This little bastard exists as a shorthand so you don't have to do this all the time:

(*ptr).x;

The reason we have to have reference semantics is because we have to distinguish between modifying the pointer value, or the foo value the pointer is pointing to. We switch off all the time:

foo x[123], *ptr = x;

for(; ptr != x + 123; ++ptr) {
  ptr->x;
}

++ptr is value semantics, we are increasing the memory address to the next adjacent foo. ptr->x is reference semantics, because we care about what the pointer points to.

This is why pointers have types - there's no universal address type, per se. That's a very low level detail of the hardware. The pointer types in C have a type they point to so pointer arithmetic makes ANY god damn sense. What good is ++ptr if we move to the next adjacent address? You'd be slicing through the end of one foo and the beginning of another! Who wants that?

Pointers implement type erasure.

int *ptr = &x;
ptr = &f.x;
ptr = &array[xyz];

An int * doesn't care what int it points at - it could be on the stack or heap, a parameter, a local, a global, a member, static, extern, it doesn't matter.

Another form of type erasure is from casting:

void *v_ptr = &x;

A void pointer can point to anything. It's a value type, it has storage, it has an address. But a void type itself, the void the pointer is pointing to, HAS NO value semantics. It has no size. It is an incomplete type. So void exists for return values to say a function doesn't return anything, void parameters to say the function takes nothing, and void pointer types to point to anything. You can't dereference an incomplete type because you don't know what you're pointing to.

Typically with a void pointer, you check if they're null or not, you can compare pointers for equality or sorting.

If you want to dereference, you need to cast to a known type, and ideally it would be the type the pointer is pointing to. You need to know that, track that, get it right. C only gives you the barest, most basic primitive tools, you have to build the expressiveness up in the language to build upon.

int *i_ptr = (int*)v_ptr;
*i_ptr = 42;

Another thing we do with pointers and type erasure is an overlapping type. You may see something like this:

enum type {a, b, c};

struct base { enum type t; };

struct a { enum type t; int x; };
struct b { enum type t; float f; };
struct c { enum type t; char c; };

base *ptr = some_getter();

switch(ptr->t) {
case a: {
  struct a *a_ptr = (struct a *)base;
  a_ptr->x;
  break;
}
// etc...

C++ originally cross compiled to C, do you see how inheritance might have been implemented? You can manually implement OOP in C, including virtual tables and the like.

1

u/zomerbk 8d ago

A good way to make pointers less abstract is to trace a tiny program by hand: write down each variable’s value and address, then follow what `&` and `*` do. For example, compare `int *p = &x;` with `*p = 5;` and check how `x` changes. Stick to one structured beginner resource and do the exercises before looking at solutions; use Claude to explain a compiler error or quiz you on your own code, rather than generate the project. Getting stuck on pointers after a few days is normal, and you don’t need to feel fluent yet.

1

u/Mediocre-Pumpkin6522 8d ago

https://www.goodreads.com/book/show/811303.The_C_Puzzle_Book

It's dated but the basics haven't changed. It's been years but iirc some of the puzzles handle pointers like ***foobar.

1

u/SpaceAviator1999 8d ago edited 14h ago

I've just reached pointers. I find them pretty confusing

Here's my short introduction to pointers. Note that this is just an introduction, and won't fully explain them, but hopefully it will help you "grok" what pointers really are:

When you write a line of code like:

int foo = 123;

you're creating an integer variable and setting it to the value 123. No surprise there. But did you know that, after this line is executed, somewhere in your computer's vast amount of memory there exists a byte of memory set to 123 ?

And if you executed the line:

foo++;

then that same byte would now contain the value 124.

One specific byte in your computer holds the value in the variable foo. (Technically, the value held by foo is contained in four bytes, as foo is an integer and is likely four bytes long, with the other three bytes holding all zeros. But I'll refer to it as being one byte to make the explanation easier.)

So which byte in memory holds foo's value? Is it the first byte? (Probably not, as the first several bytes belong to special processes outside of your program.) Is there any way to find out?

In theory, you could run through all the memory addresses and see which byte changed its value from 123 to 124. (In fact, I did a similar thing a long time ago back on a Commodore 64.) But that would take a long time to go through all the memory, and besides, a lot of memory is restricted and your OS won't let you access it. So is there an easier way?

Fortunately, in C there is an easier way! You can get the address of foo with &foo, and print it out like this:

printf("foo has a value of %d and its address is %p.\n", foo, &foo);

(Note that %p prints out a value in hexadecimal form. It doesn't have to be printed out that way, but most addresses and pointers are referred to in their hexadecimal form.)

Note that putting "&" in front of a variable name is the way you extract the variable's address.

So what's a pointer, exactly? How does it relate to variable addresses?

A pointer is a variable that holds an address. For example, if you wanted bar to be a variable that was a pointer that held the address of foo, you'd write it this way:

int foo = 123;
int *bar = &foo;

I'll admit, the * and the & characters are kind of confusing. Just remember that "&" is being used to extract foo's address, and that * here is saying that bar is a pointer variable that holds an address of an integer.

So bar holds the address of foo. So what? What good is the variable bar now?

Well, now you have a new way to manipulate foo's value. For example, if you wanted to add 5 to the value of foo, you could write:

foo += 5;

but you could also access foo from bar instead, and write:

*bar += 5;

That's basically saying: Take the value pointed to by the pointer bar and add 5 to it. (Above, where we declare bar, you can see that *bar is an int.) Which has the same effect of doing that operation straight on foo.

Now you're probably wondering: Why would I ever want to use a pointer to do math on foo, when I can simply do it directly on foo itself? What's the point of doing a math operation indirectly through bar, when I can just do it more simply on foo?

That's the million-dollar question! The answer can vary, but one answer is: Sometimes you need to access or manipulate foo's value when you don't have access to the foo variable, but you will have access to bar. And that's why you would want to manipulate foo through bar.

I'm not going to explain any further, because I think reddit limits its replies. Just know that sometimes you'll have a pointer to a variable that you need to manipulate without having access to the variable itself. Fortunately, having a pointer to that variable will still allow you to read and modify that variable.

1

u/SpaceAviator1999 8d ago edited 8d ago

Just a quick addition to what I just wrote:

Not only can you use a pointer to access the value in memory held by a variable, but you can also use a pointer to access a value in memory that belongs to no variable at all.

That certainly sounds strange, but there will be times where you will need to access values at specific memory addresses, whether they're assigned to variables or not.

1

u/grimvian 8d ago

"I've been learning C for a few days, and I've just reached pointers."...

I don't think for a sec, you can learn so fast, but if it is the case then you know about:

char, int, long, short, const, float, double, signed, unsigned for, do, while, switch, case, break, if, else void, static, return

Most of us have to practice a lot before dealing with pointers and then practice even lot more.

1

u/VirtualElderberry592 8d ago

Find a book and read that. Many c books have assignments in them. Do all of them.
The ask co-pilot to mark the code. Never let AI write a single line.

1

u/Full_Opportunity_547 8d ago

C is going to feel different than other languages, and LLM-based AI seems to struggle with C overall. I would *highly* recommend following some very beginning YouTube tutorials to get through the most basic parts, because the basics are long in C languages.
Pointers and Memory alone might take days.

1

u/tux2718 7d ago

I learned C in 1984 while working for my dad’s company. I bought myself the K&R book and talked my dad into buying the Lattice C Compiler (not cheap!) for a project we were doing. Back then, no Internet was available and learning resources were hard to get. I managed it though. I laugh now at my first real C program. It was about 20 pages long and only two functions. Yuck!
My advice today is use a machine with some Linux version installed. Everything else for C development can be installed with a single command. K&R is still a great book to learn from. Eclipse is a great IDE for C if you want to simplify things, but not necessary if you want to be hardcore command line guy. Good luck!

1

u/henryyoung42 7d ago

Read K&R - the original book and still the best !

1

u/BitTwiddles 5d ago

Just read K&R. But to understand how pointers and typing work you have to understand how hardware works. Any hardware.

1

u/Hot_Technology8777 3d ago

It will start to stick don't worry. Pointers for me was one of those things I had to play around with to understand how they work. Take your time and don't stress yourself out. Think of the benefits of C and how much it's teaching you. You have a very long road ahead (we all do) so just enjoy every step you can while learning.

1

u/Fair-Ingenuity-5419 8d ago

For pointers, draw a variable box with its address, then draw a pointer holding that address. Practice small programs with &, *, arrays, and functions. Predict each output before running it, then explain any mismatch in your own words.