Learning to Program in C

edited July 2011 in Tech & Games
So, I got a little bored and decided that I want to start programming something in C. Just reading through some of the basics now and it seems as though it's very similar to PHP from what I've seen so far, but I'm probably wrong. Anyway, I was wondering if any of you guys have any experience with C programming and if you could give me any pointers? Obviously I'll be setting myself a little challenge/project to work on to help me learn, as that tends to work well when learning a new language.

Any help/tips would be appreciated :)

Comments

  • SlartibartfastSlartibartfast Global Moderator -__-
    edited July 2011
  • BaconPieBaconPie Regular
    edited July 2011
    Yay! C is my favourite language because it's so simple. Only problem is that it doesn't look after you. It's up to YOU to make it look pretty.

    C invented the braces style programming, so other languages get it from C (Java, C++, C#, PHP, Javascript, etc).

    First thing you'll notice coming from PHP is that there is no object support (C is very old). This means that high level stuff can go out of the window. You want to write a game? Do it in Java. C just leaves too much up to you. But don't let that scare you off. It's a sexy language because it's so brief.

    K&R is pretty much the only book you should ever buy on C.

    Some basics:
    Functions in C have the following syntax:
    <return value> function_name(arguments)
    {
      // a comment
      /* another multi
         line
         comment */
      int number = 5;
    }
    

    What do you want to know about? I don't want to type out a Hello World if you know the basics.

    Do you know about makefiles? They will make your life easier. To compile I type:
    $ gcc -c helloworld.c
    $ gcc helloworld.o -o helloworld
    

    The first line compiles the C source into an object file (not related to OOP). The object file is not linked to any libraries. The second line links the object file to any libraries and outputs the completed binary file to helloworld.

    GCC is a compiler AND a linker.

    It also supports doing this in one go:
    $ gcc helloworld.c -o helloworld
    

    Make is a program that tries to make it easier when you're compiling code. So, if you type:
    $ make helloworld
    

    (NOT make helloworld.c <- a cause of many headaches)

    This will compile helloworld for you because make knows about basic C stuff. If you want to get even more fancy you can write a makefile. Make will look for the makefile first and follow any instructions in there.

    Here is a makefile for your hello world:
    $ cat makefile
    CC=gcc
    CFLAGS=-Wall -g -std=c99 -pedantic-errors
    LDFLAGS=
    
    helloworld: helloworld.c
            $(CC) $(CFLAGS) $(LDFLAGS) helloworld.c -o helloworld
    

    The syntax is basically:
    <make command>: <required files>
    <tab> <commands you want to run>
    

    The make command is the thing you type after 'make'. For example:
    $ cat makefile
    
    CC=gcc
    CFLAGS=-Wall -g -std=c99 -pedantic-errors
    LDFLAGS=
    
    helloobject: helloworld.c
            $(CC) $(CFLAGS) $(LDFLAGS) -c helloworld.c
    
    $ make helloobject
    $ ls
    helloworld.c helloworld.o
    

    LDFLAGS is where you stick you linker flags. So if you need to use the maths libraries then you put in "-l m" on that line.
  • BaconPieBaconPie Regular
    edited July 2011
    I'll just fire some random bits of knowledge out.

    Pointers
    A pointer is a variable type that points to another variable. I'm a fairly low level guy when it comes to CS, so I find it easier to think of a pointer as a memory address to a fixed size variable (because that's what it is). An example will help:

    You define a pointer like this:
    char * myfirstpointer;
    

    What this says is, I want a variable called myfirstpointer. That variable is going to contain an address of one single byte (a char is one byte in C). Read the * as pointer. So that line reads (in english): "character pointer myfirstpointer;".

    Lets declare a variable that is going to store an address (a pointer) of an interger:
    int * integerpointer;
    

    This pointer, will be the same size as the char *. The only difference is the size of the variable it points to. I.e, it's still an address, but now whatever address we are pointing at isn't just one byte large it is two (or however big an int is).

    A "void *" (void pointer) is, again, an address but this time, we don't know how large the piece of data is. Void pointers shouldn't really ever be used but you will come across them (some functions return void pointers and require you to 'cast' them to a more sensible type).

    You use the & operator to get the address of a variable (a pointer).
    int number = 5;
    int * pointer = &number;
    

    Now 'pointer' contains the address of 'number' (I know this doesn't really seem that useful right now but it will become clear...).

    You can get the contents of a memory address using the dereferencing operator. Get ready to blow your brains out all over the wall. The dereferencing operator is *. :|

    FUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU! Talk about confusing those new to the language! Thankfully, you hardly ever use it and it's also obvious when it's a dereference because it's not in a variable deceleration.
    int number = 5;
    int * pointer = &number;
    int another_number = *pointer;
    

    Now, another_number contains 5.

    Get it?

    Some gotchas:
    You can declare more than one variable of the same type on one line:
    int number1, number2;
    

    Since we are creating a new type with the pointer operator you'd think then we could declare multiple 'pointer types' on one line as well:
    int * pointer1, pointer 2;
    

    This doesn't work because the * only applies to the adjacent variable. It is for this reason that many people like to couple the * operator with the variable. I don't it looks confusing. Either A) don't declare pointers on the same line or B) put a * before each variable:
    int * pointer 1, * pointer2;
    

    UNDER NO CIRCUMSTANCES DO YOU EVER DO THIS:
    int * pointer, number;
    

    Although it is valid C, it's ugly. C won't complain but everyone reading your code will. It's not big. It's not clever. It's hideous, unsexy, unreadable, dirty, wrong, disgusting. Don't declare variables of different types on the same line k?

    It would probably make sense if they had a 'pointer type' rather than 'modifying' current types with the *. But they don't. Deal with it.

    Printf
    printf is a function that prints stuff in a specific format. It is included in the standard input/output library. Before I talk about that though, lets talk about functions. When you use a function in C you need to declare it before you use it (yeah it's a pain, but remember, C is simple, none of that fancy pre-compiler interpreting fizfuz). There are two ways of doing this; either you just write your function above wherever you are using it, or use a function prototype. A function prototype says to the C compiler "I know about this function, it returns this type of value and takes these values as arguments". An example:
    $ cat functions_above_call.c
    int sum(int number1, int number2)
    {
      return number1 + number2;
    }
    
    int main(void)
    {
      int answer = sum(1, 3);
    }
    
    $ cat function_prototype
    int sum(int, int);
    
    int main(void)
    {
      int answer = sum(1, 3);
    }
    
    int sum(int number1, int number2)
    {
      return number1 + number2;
    }
    

    Ok, back to printf. Like I said, the actual function for printf is in the standard input/output library. This library is linked into your program when you use gcc. Unlike other libraries you don't have to include a "-l " flag to the compiler because stdio (standard i/o) is used so often it just does it automatically. But what about the thing I just said about functions? You have to declare them before you use them. This is done through header (.h) files. These are basically just big lists of function prototypes of the functions that are in the already compiled libraries. stdio.h contains the function prototype for printf (seriously, check it out at /usr/include/stdio.h).

    Long story short, when you want to use the printf function you have to include the stdio.h header file which contains the printf function prototype. This is done with the #include keyword. You put header files that are from the search path in <> brackets and header files from the local directory (in case your writing you own) in double quotes "".
    $ cat helloworld.c
    #include <stdio.h>
    
    int main(void)
    {
      printf("%s\n", "Hello World!");
    }
    

    printf takes it's first argument as a fomat specifier and then the rest as variables to print in the order specified. Examples:
    printf("%d\n", 5);
    printf("%d\n%s\n", 6, "hello");
    

    %f is float
    %s is string
    %c is char
    %d is int (wtf)
    \n is newline
  • BaconPieBaconPie Regular
    edited July 2011
    Struct
    A struct is a C construct that allows you to group other variables within it. Think of a struct as a box that you can put other variables in. To define a struct you use the following syntax.
    struct point
    {
      int x;
      int y;
    };
    

    The semi colon is there because C ignores all (well, most) whitespace. I.e. the struct can be declared in one line. Don't do that though.

    To declare a struct you do so as you could any other variable:
    struct point origin;
    origin.x = 0;
    origin.y = 0;
    

    Notice how the type of the struct is 'struct point' not just 'point'.

    A struct can have many different types of variable:
    #include <stdio.h>
    
    struct book
    {
      char * title = "Tales of Glass";
      int pages;
      double price;
    }
    
    int main(void)
    {
      struct book mybook;
      mybook.price = 6.99;
      
      printf("%s\n", mybook.title);
    }
    

    Union
    A union is just like a struct except only one of it's variables are in use at any given time. Only space big enough for the largest variable is allocated, so if you know you're only going to be using one variable at a time then use a union.

    Malloc/free
    Memory Allocate is a function that returns a pointer to some dynamically allocated memory of a specified size. The place were the memory is allocated is called the 'heap' and is in a separate memory location than the memory given to your program by the operating system.

    The follow piece of code allocates some memory big enough for one integer. By allocate, I mean that the operating system has flagged that piece of memory as in use and will not overwrite it with anything else. It then returns the memory address of the new space and I'm passing that into a pointer called 'number_pointer'.
    int * number_pointer = malloc(sizeof(int));
    

    To write something to that address we can use the dereference operator:
    *number_pointer = 5;
    

    Once you are done with the piece of memory then make sure you tell the operating system that you are done. To do this you pass the pointer to the function free() which does all the hard work! When you do this, the operating system flags the memory as not in use and it is then free to be used by other processes (running programs).

    Although, the memory is free'd anyway once your program finishes (because the operating system is smart like that) you still should free your pointers. Why? Because if you don't then any memory that you do allocate remains tied to your program until it dies.

    A good example is if you were writing a tabbed web browser and with each new tab you allocated some space for a struct containing information about that tab (or, if you were being sensible and using an Object Oriented language, you would be allocating space for an object. An object is basically a struct but with functions [which are called methods] as well as variables). Imagine now that whenever you closed a tab, you did not free the memory used by this tab. The browser stays open, and so the memory that was used by the now dead tab is still flagged as in use and cannot be used for other things by the operating system. This is called a memory leak. Imagine if you kept on re-opening and closing tabs; eventually you would run out of memory and your computer would crash or the operating system would kill your process.

    MAKE SURE YOU USE FREE! Every single time you write malloc, make sure there is a free somewhere too. You can use the program valgrind to watch your memory usage and check if you are freeing everything; more often than not you will have memory leaks.
  • edited July 2011
    Holy fuck! That's a lot of awesome information, you should write it into a guide or something. Thanks for sharing it! :D
Sign In or Register to comment.