You have two distinct issues: C programming and low level programming. They are separable...
There must be thousands of tutorial on the C language on the Internet. If nothing else, maybe one of the courses at Udemy. Get a copy of "The C Programming Language" ANSI Edition (you should also have the classic edition at some point). Pointers are both the worst and the most powerful concept in C.
Want to learn C programming? Start writing C code. Write lots of C code. Thousands and thousands of lines of C code. Then you will know C programming. The path isn't short.
Then there is the matter of low level programming. Jumping past the Arduino library stuff and heading toward bare metal. The other day I was looking at this Instructable and while the result is not especially interesting, to me, what is presented seemed to be an excellent tutorial. At least it shows what is involved:
https://www.instructables.com/id/Girino-Fast-Arduino-Oscilloscope/One of the things you should see at first glance is the reliance on the datasheet. Users of Arduino libraries are shielded from the necessity to actually read and understand a datasheet. No longer! Look at all the bit-twiddling in the registers. You get this information from the datasheet. Exercise: Match up the code and comments to the related section in the datasheet until you actually understand what is happening and why.
Buffers, in this case circular queues, are covered in EVERY book on data structures and keeping the pointers straight is critical. There MUST be examples of receive data queues on the Internet. They all work the same. There is a HEAD pointer, a TAIL pointer and an array that is conceptually wrapped into a circle.
A character comes in, the UART generates an interrupt, the interrupt code fetches the character and stuffs it into the queue and maybe sets a flag or count variable. The mainline code eventually wants to process the character so it extracts it from the queue, updates the tail pointer and the count (if any). The counter idea is in addition to what is usually taught - it is simply a way to find out if there are chars in the queue without havimg to do arithmetic on the pointers.
https://www.studytonight.com/data-structures/circular-queueHere's a free HINT: Disable interrupts in the mainline code before trying to extract a char. You don't want the head pointer changing in the middle of the extraction.
Here's another free HINT: Make the queue length a power of 2. 2, 4, ..., 64, 128 and like that up to whatever size you want. It makes doing the wrap-around arithmetic on the pointers as easy as an AND operation to kill the overflow when the pointer runs off the end.
The head and tail pointers are actually indices into an array, not usually actual address pointers. Suppose your queue had 16 entries (0..15). Then when you wanted to update an index, say tail, the math looks like
tail = (tail + 1) & 0x0f;
Notice how, if tail was already 15 (0x0f) when it is incremented to 0x10, it is out of range but that's ok because the AND operation throws out the high bit and the result is 0x00 and that's exactly what we want.
If your queue had 256 entries the math would be
tail = (tail + 1) & 0xff;
As soon as the value increments to 0x100, it is brought back into range.
https://www.programiz.com/c-programming/c-operatorsGood luck!
There are better forums here at EEVblog for programming related issues.