2020-02-08 16:43:26 (edited by amerikranian 2020-02-08 16:44:06)

So we are starting to learn C over here, or at least looking at it do to the various issues our course is having in my school. It is pretty interesting so far. My problem? Integer input.

#include <stdio.h>
main()
{
    int enteredInt = getIntInput();
    if (enteredInt != NULL) printf("You entered %d! Yea!", enteredInt);
    else printf("How could you not enter an integer?");
    return 0;
}

int getIntInput()
{
    int a;
    char term;
    printf("Enter your number.");
    if(scanf("%d%c", &a, &term) != 2 || term != '\n')
    {
        return NULL;
    }
    return a;
}

This looks perfectly valid to me. However, here is what I get when compiling the program:
.\input.c(5): warning C4047: '!=': 'int' differs in levels of indirection from 'void *'
.\input.c(17): warning C4047: 'return': 'int' differs in levels of indirection from 'void *'
Both of the mentioned lines seem to complain that I am comparing with  or returning a NULL. My dilemma is this:
If I use a normal scanf function, I have no way of telling if it actually fails. It returns 0 if it does, but I could also type in a 0 as my value. So... how am I supposed to tell when the function fails?
I found part of the code in the getIntInput function online, and while it does prevent you from typing in invalid integers, I am also locked out of typing a 0 (do to NULL equaling that value I suppose).
So, suggestions?

2020-02-08 17:07:11

Why aren't you using an array for input and then scanning the input as an integer? Remember, everything in C is arrays. You'd use the fgets() function to get input from the user and then scan that input as an int.

What game will hadi.gsf want to play next?

2020-02-08 17:59:10

I am not using fgets because this is the first time I am seeing the name. Remember, I started to learn this a really really short time ago.
I am aware that everything dealing with strings is an array in C. What I fail to understand, however, is why must I use an array here? My input method works just fine... aside from the NULL and the 0 problem.

2020-02-08 18:32:52 (edited by stewie 2020-02-08 18:35:29)

Yeah this is an annoying problem in C that comes up a lot. You could set getIntInput to return an int pointer, like

int *getIntInput() {
...
}

then just alter main accordingly. Alternatively you could set up some kind of struct with another int signifying whether or not an error occured.

Deep in the human unconscious is a pervasive need for a logical universe that makes sense. But the real universe is always one step beyond logic.

2020-02-08 18:59:43

How about taking a pointer to the integer as an argument, and simply returning true or false from the function to indicate the outcome? If you actually return a pointer from the function, you'll have to start using malloc and free for a task where it is not needed. If you use a pointer as an output parameter, the user can declare it on the stack as usual without the need for dynamic memory allocations.

Kind regards,

Philip Bennefall

2020-02-08 20:01:10 (edited by amerikranian 2020-02-08 20:01:30)

Ur, can you all do me a favor and explain the concepts you all are talking about? int*? Pointers? Malloc? I am so confused. Is it really that difficult? LOL

2020-02-08 20:40:47

Yes, C is a confusing mess when you learn it for the first time. Are you currently taking a course  in it? It would be of use to let us know what you do understand thus far of the language. It's an entirely different beast than say, Java.

What game will hadi.gsf want to play next?

2020-02-08 20:54:12 (edited by amerikranian 2020-02-08 20:56:32)

I know variables, operators, if statements, and a bit about functions. We just learned scanf and went over operator precedence.
I also have a strong background in Python, with some Java knowledge to boot, though neither is currently helping me in learning this language. Basically, if you equate a concept to Python or Java, I will probably understand it.

2020-02-08 21:31:26

At 8 its kinda difficult to learn c coming from python but it will definitely help you out

2020-02-08 21:45:10

Python is actually scripted based on C. There's a lot that happens when you're writing in high-level programming languages.
The way I learned it when I took the C programming course at my university was that everything is an array in memory, period. We learned how to get input from the user, reading/writing binary files, then got into structs, pointers, and so on.

What game will hadi.gsf want to play next?

2020-02-08 21:52:13

Alright so here it goes, though you could ask Dr. Google for more fancy schmancy stuff, but generally in C/C++ you have memory management. This means that variable allocation is divided into two primary parts. Stack and heap. The stack part is where you usually define your static variables, and what I mean by static is that the space for the variable is already predetermined in compile time. But then you have the heap part and there comes a dynamic allocation where the space is allocated during run time and you could dynamically alter the space as long as you play by the rules. So, in order to access a dynamically allocated  variable you have to use pointers or references. Basically what this means is that you define a variable name of type int eg, and using the * after the type would turn it to a pointer. Now you have to point this variable in the right direction, which is the space you have allocated for it. Generally you could use pointers/references to point to anywhere you want, it shouldn't necessarily be a dynamically reserved space. In C, malloc is the function which you allocate memory, free is the function which you deallocate memory. What post 5 means by passing a pointer to the input function is something like int getIntInput(bool* OK). Then in the function body you could do something like
    if(scanf("%d%c", &a, &term) != 2 || term != '\n')
    {
        *OK=false; //* is the dereference operator. It says something like "hey, go to this guy's location and set his value to *false." Although you have to pass an already allocated bool to it, otherwise what you dereference could be some random *block, which would piss your computer off, resulting in unexpected crashes/ horrible memory leaks/ etc.
    }
Look at This page to see this in action.

2020-02-08 22:37:51

@1 If you don't know how to use pointers yet you can use a global variable, as your problem is returning 2 values and in the getIntInput function return if scanf succeeded or not, like this:
Before the main function:
int got_number;

And after all the other functions:
/* Returns 0 if your number couldn't be read or 1 if it was read correctly. */
int getIntInput()
    {
    char term;
    printf("Enter your number.");
    if(scanf("%d%c", &got_number, &term) != 2 || term != '\n')
        {
        return 0; /* scanf failed */
    }
    else
        {
        return 1; /* Number was read correctly */
    }
}

Now, you could use your function as follows:
if (getIntInput() && got_number == 1) /* User entered the number
correctly and it was 1 */
Hope it helps and it makes sense for you. If not feel free to ask again.

2020-02-08 22:59:01 (edited by Ethin 2020-02-08 23:05:51)

Long post ahead. Just saying.
So, Python won't really help you here when learning C. A lot is hidden from you in Python, all of which is pretty important in C.
In C "everything is an array". Though this is an oversimplification and slightly inaccurate, it is nevertheless true. An array is literally a stack-allocated or dynamically-allocated pointer. A string is, also, an array of characters; as some article I once read put it, "Strings, in C, were an afterthaught". But I digress.
When a program is started on a computer (even an operating system/kernel of one) it is allocated a stack and a heap. The stack is where "local/global" static variables are allocated; that is, declaring a variable of type int named "i" (i.e. "int i = 2") creates a stack variable. The stack is also where functions are allocated and called. That is, a function is not allocated "on the stack", so to speak, but its "frame" is placed onthe stack. Every time a function is called, its "frame" is placed ("pushed") onto the stack, and when that function terminates its frame is removed ("popped") off of the stack. Hence, in recursive functions (functions that call themselves) there is a possibility of "stack overflow", that is, so many frames are pushed onto the stack that you fill the stack up, causing your next function call to be written somewhere else in the programs memory that is not the stack (because the stack is full). The heap is a huge chunk of memory set aside for each and every program executed on a computer. No program (bar the global program execution system, whatever that may be, of the OS) knows the "real" heap size; when a program is launched a chunk of the heap is allocated for that particular program. The heap, itself, however, is dynamically allocated and can change size depending on what the computer is doing at the time. The heap is used for dynamic memory allocation and it is accessible either through pointers, which I'll describe next, or through the memory management functions malloc, calloc, realloc, and free (though in C++, this is extended to add aligned_alloc and the C++ variants of the above four functions). Do note, however, that there are no C++ equivalents to calloc or realloc. In sum:

  • malloc() allocates memory and returns it to you. It does not do anything with that memory.

  • calloc can be used for allocating arrays, but can also be used to allocate ordinary blocks of memory. This function allocates, zeros, and then returns that memory to you. Its usage is a bit more complicated than malloc and there are very few places where I've ever seen it used.

  • realloc reallocates previously allocated memory. This includes shrinking allocated memory or expanding the allocation. It isn't difficult to use correctly though can be dangerous if misused.

  • aligned_alloc allocates an aligned block of memory. This probably won't be very useful to you right now.

  • free frees allocated memory. You should *only* use free on memory that has either been allocated by you or that you have been instructed to free by libraries your using. Just remember the rules of memory management -- which I'll outline below fater I describe pointers.

Now, pointers. Pointers are just special variables in a C program that point to memory addresses. The declaration:

int*i = 2500;

Does not create an integer variable with the value 2500. Instead, it creates a pointer that is an integer that points to the memory address 2500. What value is at that memory address is unknown (since it is highly unlikely I have access to that memory address). There are reasons for this, involving paging and the way your processor manages memory, but I won't get into that unless you want to really know how that works. Typically you either utilize pointers by:

  • Allocating memory malloc, calloc) or receive allocated memory from a library; or

  • Creating arrays (which are also pointers) which are allocated at compile time.

The second use case is one of the most popular use cases. How it works is simple:

// Allocate a string
const char* text = "hello!";
// or
char* text = "hello!";

Both of these declarations do the exact same thing: they set aside "stack memory" for an array of char with the characters 'h', 'e', 'l', 'l', 'o', '!'. The only difference is the first one, where I use the "const" keyword.
The first method is necessary with libraries and in situations where the information your manipulating most likely won't fit on the stack and/or is external to the program (i.e. the body of an HTTP request or a file from disk). In this instance, you need to use one of the above memory allocation functions. As an example, assume that we're loading a file from disk and we don't know the size (say, its passed to is by a caller). We might do:

FILE* f = fopen(filename, "r");
char* data = (char*)malloc(size);
fread(data, size, 1, f); // no error checking!
fclose(f);
return data;

In this sample, we:

  1. Open the file specified by the filename variable in "read" (text) mode.

  2. Allocate size bytes of memory for our storage of the contents we're about to read.

  3. read the contents of the file into the data variable using the fread function.

  4. Close the file handle we've gotten from the OS using the fclose function.

  5. Return the data we just read.

In this instance, the caller of our function must free the data that they've gotten from this, and so we should specify that in our documentation of this function. Which dovetails nicely into the rules of secure and safe memory management. For those, I refer you to chapter 9 of the SEI CERT C secure coding standards, first edition, for actual examples and in-depth analysis (including exceptions). I'll post them here though since your already reading this post (the summaries of them, really):

  1. Allocate and free memory in the same module at the same level of abstraction

  2. Store a new value in pointers immediately after free()

  3. Immediately cast the result of a memory allocation function call into a pointer to the allocated type

  4. Clear sensitive information stored in reusable resources returned for reuse

  5. Do not perform zero-length allocations

  6. Avoid large stack allocations

  7. Ensure that sensitive data is not written out to disk

  8. Ensure that the arguments to calloc(), when multiplied, can be represented as a size_t

  9. Use realloc() only to resize dynamically allocated arrays

  10. Do not assume memory allocation routines initialize memory

  11. Use a pointer validation function

  12. Do not access freed memory

  13. Free dynamically allocated memory exactly once

  14. Detect and handle memory allocation errors

  15. Use the correct syntax for flexible array members

  16. Only free memory allocated dynamically

  17. Allocate sufficient memory for an object

Some of these rules you may not need right now (i.e. the flexible array members rule) but all of these are important and should be remembered when using memory management functions in your future adventures with C. The one about immediately type casting results of memory allocation to the type your actually using is not necessary so long as you've declared the variable as that type, as I've done above. The type cast ((char*)malloc(...)) is not truly necessary, though you should probably do it just to get rid of the warnings your compiler is bound to throw your way.
I hope I helped -- at least a little! smile And I'd be happy to explain something that I've written here that you don't understand in this post further too, of course.

"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-02-09 02:57:06

All this pointer stuff and stack stuff is probably confusing. As people have already mentioned, C is different enough from Python, JavaScript, etc, to have to endure a steep learning curve, especially if you're accustomed to dealing with higher level languages. I haven't really tested out your code, but I think this code essentially has the problem of function scope and the dreaded pointer management problem. Pointers are very tricky, and others have already done a good job explaining some of the intricate details. As an experiment, try directly printing the output variable as populated by the call to scanf after your failure condition in your getIntInput function. In other words, try printf(a) in that function. Additionally, I'm not sure you can return NULL in a function with the defined int return type. There is also the issue of scope, which I briefly mentioned. I'm not sure that a populated memory address is retained when the function exits. In other words, this relates back to the stack which others have explained. My recommendation would be to pass a temp variable to the function that calls the scanf function. I think this will allow the value to be retained for your comparisons in the main function. These are some suggestions as I can't really type a more an-depth explanation at the moment.

2020-02-09 05:58:24

14 is pretty much correct. Though the stack and heap discussion is quite complicated, and we delved pretty deep into the discussion on pointers, all of it was relevant and important to know. Any good C book goes into pointers fairly early, and the Rust book discusses the stack and heap pretty much at the get-go. As for returning NULL, the compiler is pretty much telling you that you shouldn't do this. The documentation on Warning C4047 confirms this:

MSDN wrote:

'operator' : 'identifier1' differs in levels of indirection from 'identifier2'

A pointer can point to a variable (one level of indirection), to another pointer that points to a variable (two levels of indirection), and so on.

Personally I feel that this warning is not actually a "problem" per see; and doesn't actually describe the problem. This is, unfortunately, something MS compilers are well-known for. GCC, I think, describes things much better:

test.c:3:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
test.c:5:22: warning: implicit declaration of function ‘getIntInput’ [-Wimplicit-function-declaration]
test.c:6:20: warning: comparison between pointer and integer
test.c: In function ‘getIntInput’:
test.c:18:16: warning: returning ‘void *’ from a function with return type ‘int’ makes integer from pointer without a cast [-Wint-conversion]

These warnings are pretty clear cut, though I shall make some notes about them because the compiler is trying to tell you something without outright saying it:

  • Warning 1: your main() function declaration should always have a return type specifier (i.e. int main()). When omitted, the return type defaults to int, as GCC indicates. Though some programmers write this way, it is only accepted for backwards compatibility. The C++98 standard even states: "At least one type-specifier that is not a cv-qualifier is required in a declaration unless it declares a constructor, destructor or conversion function.     There is no special provision for a decl-specifier-seq that lacks a type-specifier or that has a type-specifier that only specifies cv-qualifiers. The "implicit int" rule of C is no longer supported." So, in C99 and above (I would encourage you to practice C18, as it is the most current standard) this is an error (test.c:3:1: error: return type defaults to ‘int’ [-Wimplicit-int]).

  • Warning 2: This next warning is also for backwards compatibility and is an error. Unlike most languages, C compilers parse programs top-down as the file is read. As such, line 5 of your code is calling a function that is not (yet) defined. This can be alleviated by using function prototypes.

  • Warning 3: Another (actual) error. NULL is a pointer, not an integer. It is illegal to compare pointers with values; you may only test pointers and pointers or values and values but not both.

  • Warning 4: a slightly trickier (but actual) error. This line returns NULL. As I said above, NULL is a pointer, and you are rturning a void* pointer in a function that returns int without casting it to an int first.

So, in sum, this code is malformed. It may compile but that does in no way mean it is actually correct, and that is something to be aware of when writing C code. This is (generally) the major problem with C and C++ compilers; you'll never be able to get everyone using the latest standards without forcing the issue, so instead of doing that like they should've, ISO (in conjunction with compiler designers) decided it would be a brilliant idea to heap on compatibility layer after compatibility layer to ensure that old code *always* compiles. The correct code for this program is:

#include <stdio.h>

int getIntInput();

int main() {
    int enteredInt = getIntInput();
    if (enteredInt != (int)NULL) {
printf("You entered %d! Yea!", enteredInt);
    } else {
printf("How could you not enter an integer?");
}
    return 0;
}

int getIntInput() {
    int a;
    char term; // This variable is unneeded but kept around
    printf("Enter your number.");
    if(scanf("%d%c", &a, &term) != 2 || term != '\n') {
        return (int)NULL;
    }
    return a;
}

This, still, raises two warnings:

test.c: In function ‘main’:
test.c:7:23: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
    7 |     if (enteredInt != (int)NULL) {
      |                       ^
test.c: In function ‘getIntInput’:
test.c:20:16: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
   20 |         return (int)NULL;
|                ^

So, as a recommendation I'd just get rid of the NULLs in your code. (For reference, the flags I'm using to compile with GCC are -pedantic-errors, -std=c18, -Wall, and -Wextra.)

"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-02-09 06:57:10

Just to put the intricacies of C into perspective, there are different levels of programming languages. Python, Java, etc, are all high-level languages. The closer you get to English, the higher level a language is. C is lower than Python, but above assembly. When you're calling print() in Python, it's just using the equivalence of printf() in C. When you call printf() in C, it's equivalent to what I believe is assembly's X86 version of storing the appropriate values in registers and making a kernel call using an interrupt command.
Put simply, you can't relate everything to Python or Java. Those languages are intended to make the development process easier. Lots of practices (or lack thereof) is what contributed to the birth of high-level languages. Python, Java, any object-oriented language is known as an "unmanaged" language. You may be familiar with garbage collection, where, in Java, the JVM runs a cyclical process that frees up memory that was occupied by unused data. Developers had this nasty habit of using memory and not cleaning up after themselves. So, low and behold, some genius(s) decided it just made sense to save everyone time and make that an automated process. Languages handle this differently, but it's something devs don't concern themselves with. This is vastly different than C, where you need to make sure you clean up after yourself.
The reason why it's important to understand how C works is for this very reason among many. It's important to understand what is actually happening behind the scenes when you have code that looks like:

Class myObject = new myClass();

What some intro level students are either not taught or neglect to understand is that this code is instantiating an object, or creating it in memory. It then assigns the address of the object to the reference variable, myClass. Passing by reference can be an issue, so it's important to understand what is going on when you're coding. Learning C really helps with these concepts, they're just underneath all the confusion of C...Lol.
Plus, there are ways to do things and do things efficiently. If you're taking a C course, I'd expect that you have or are taking a course in data structures. Part of data structures is understanding when to use what, and why that's the case. Linked lists in theory could be better than arrays, but the way they're handled in memory can make them horribly inefficient in practice.

What game will hadi.gsf want to play next?

2020-02-09 08:33:43 (edited by Ethin 2020-02-09 08:36:30)

@16 nailed it! I'll demonstrate the C program and then the ASM instructions just for the fullest understanding to what 16 is talking about, so another long post here.
The C program:

#include <stdlib.h>
#include <unistd.h>
int main() {
int len = strlen("Hello world!");
write(stdout, "Hello world!", len);
exit(0);
}

Is equivalent to the asm program:

section .data
msg db "Hello world!"
section .text
global _start
_start:
; Calculate length of string for write syscall
mov rdi, msg
xor rcx, rcx
not rcx
xor al, al
cld
repnz scasb
not rcx
dec rcx
mov rdx, rcx
; RDX register now holds length of string excluding NULL
; Write msg to stdout
; write() function prototype: ssize_t write(int fildes, const void *buf, size_t nbyte);
; Related: pwrite(3).
; Inputs:
; filedes: file descriptor to write to (stored in RDI register)
; buf: buffer to write (memory address stored in RSI)
; nbyte: number of bytes to write (stored in RDX via calculation above)
; Outputs:
; Number of bytes actually written to fildes (stored in RAX)
; Fill arguments
mov rsi, msg
mov rax, 1
mov rdi, rax
; Call function
syscall
; Terminate program
; Function prototype: void exit(int status);
; Inputs:
; status: exit code (stored in RDI)
; Outputs:
; No return value
; Set RDI to 0 by XORing it
xor rdi, rdi
; Set RAX to hold the system call
mov rax, 60
syscall

The assembly language version of this code does seem a lot more complicated. As you can see, C simplifies this quite a bit. However, its still good to know assembly language, and you'll learn it in your computer organization class (at least that's what my class is called). The general format of the instructions is instruction rd, rs, rt; that is, instruction destination, source, type. Now that you understand that you then need to understand what each instruction does.

  • mov (move): move contents of source to destination. Syntax: mov dst, src. dst and src can be registers, memory addresses, etc.

  • xor (exclusive or): XORs src with dst and places the result in dst. Syntax: xor dst, src

  • not (not): logical not. Negates dst and places the result in dst. Syntax: not dst

  • cld (clear direction): clears the direction flag used to determine the direction for data transfer. When the flag is set (1), data is transfered from highest index to lowest (right-to-left); when it is clear (0), data transfer goes from the lowest index to the highest (left-to-right). When the flag is cleared this is called "auto-incrementing" mode; when it is set it is called "auto-decrementing" mode.

  • repnz (repeat while nonzero): repeat string-operation until tested-condition. REPNZ repeats the given assembly instruction until the zero flag is set. There are two (and probably more) other "repeat string" instructions, REP and REPZ, for "repeat while equal" and "repeat while zero". Syntax: REPNZ, REP, REPZ <instruction>. According to this page there's also REPNE (repeat while not equal). I suspect there are others I'm unaware of.

  • SCASB (scan string bytes): searches the item in the AL register for what is at the memory address pointed to by the EDI or RDI registers or the ES and DI registers together; that is, you either place the memory address in EDI or RDI or put the lower 8 bits in ES and the upper 8 in DI, set what your looking for in AL, and then execute this instruction. No arguments; must be used with a "repeat while..." instruction.

  • DEC (decrement): does the opposite of INC (increment) and replaces the source with the result. Syntax: DEC/INC <register>.

  • SYSCALL (system call): executes the system call. Though this instruction has no operands, how it works is OS-specific.

Though this does look overly complicated, it really isn't. The very difficult part is remembering the various instructions (which you probably won't be able to do, which is why the developer manuals are available) and actually writing programs that work. That will take a lot of time, but as some fun and exercise I'd encourage you to at least try it. Linux may probably be the easiest to write for. Arguments are stored in RDI, RSI, RDX, R10, R8, and R9, system call values (available at websites like this one) are stored in RAX, and return values are stored in RAX.

"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-02-09 16:40:40

Thank you for that breakdown, @17! Once you learn C and assembly, how computers actually handle "programming" makes a hell of a lot more sense. If you're using GCC to do any of this, especially C, it's great to have things like GDB to get the debugging experience of programming beyond what compilation errors will tell you.

What game will hadi.gsf want to play next?

2020-02-09 16:57:50

@Ethan, thank you for providing the fixed versions of the code. I am using the visual studio developer prompt to compile because I never got GCC to work on my machine. I saw the int before the main function, but at some point I removed it to see if the program broke and it didn't. I've been leaving it off ever since going by the logic of if it works why bother. Thanks for pointing out the function order, I actually was really surprised that it ran successfully because as you have pointed out, it was not yet defined.
You all have mentioned memory leaks. Is there any way to detect them? As far as I know, the compiler does not really warn you about them except in specific cases, does it?
Okay. That was a lot of info to sort through. Never the less, thanks to all for the explanations, as complicated as they are. I have fixed my issue and have learned a lot in the process. I also realize now how much I really took for granted. Thank you all. I will be sure to post of any more troubles I encounter along the painful way of knowledge.

2020-02-09 18:13:27 (edited by kaigoku 2020-02-09 18:31:25)

@19, I am glad your question was answered and you learned from this experience. Sometimes, I tend to leave out a lot in my explanations because I don't want to overwhelm new learners. But sometimes, some background may help. In addition to the Assembly, C, and higher level language hierarchy, there are different assembly language systems and specifications for each architecture. These specifications are referred to as the Instruction Set Architecture (ISA) of a system. Two well-known architectures are ARM and X86. Different compilers generate appropriate assembly for machines. GCC, for example, is not just a compiler, but a linker, an assembler, and it includes a debugger. The compiler does all the converting from the languages that you are used to seeing to the assembly language flavor. of the machine. Prior to that, however, the preprocessor makes sure all files are included, macros are expanded, and so on. Then, the compiler hands the program over to the assembler, which generates the machine code. Finally, the generated output goes through the linker and links system libraries, etc to generate an executable. There are some intermediate steps I missed, but this is the general idea. I get really passionate about this because if you also do the Computer Engineering track in school, you'll even learn about even lower level stuff like logic gates, transistors, FPGAs, and so much more. I built my own minimal computer using these bare electrical components, created the machine's ISA, wrote an OS for it, wrote a compiler for it, and got it to run simple programs. Just FYI, I had a lot of resources as I was on an electrical engineering track as well. These exercises can really give you a handle and mastery of every part of the computer and help you realize the full potential these human-made innovations can ultimately reach. These are just some things to look forward to in your Computer Science track.

2020-02-09 23:06:35

@19, yes, there are lots of ways of detecting memory leaks. You can replace the meory allocator with one that can, for example. You can also use well-known tools like Valgrind which will also help you find them (though it will make your code much slower in the process).

"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-02-10 01:52:45

If you can muscle your way through C and the vast, strange universe it occupies, then you're doing well. It's always a rude awakening jumping from the comforts of Python to C. Things don't always make sense, random things can happen, it's a mess. But C is where all these wonderful languages came from, so we can't be too frustrated with it.
big_smile
Certain things may seem confusing, like pointer arithmetic. This is just array indexing, but it can be expressed in multiple ways in C. In fact, you can accomplish lots of tasks in C with very obscure code. There's even
a competition
where programmers will make their code so obscure you have no idea what they're doing. This just shows how weird C can get.

What game will hadi.gsf want to play next?

2020-02-10 02:09:13

Agreed with 22. Learning (and then mastering) C is one of the best things you will ever do. You'll be able to sit down and read the code of literally anything and understand, on a primitive level, how it works. You'll also learn how to write fast, performance-critical code. Python is wonderful, but the shear level of abstraction leaves something to be desired. You have, for example, no control over memory management. You can't say "I want a string of 5 bytes" and know that your getting a string of 5 bytes, for example. You might get a string of 8 or 12 bytes, for example. The journey of learning C is dangerous and magical all at once.

"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