Page 1 of 2 12 LastLast
Results 1 to 10 of 11

Thread: C for Beginners : Part 3 : Portability

  1. #1
    Very good friend of the forum Virchanza's Avatar
    Join Date
    Jan 2010
    Posts
    863

    Default C for Beginners : Part 3 : Portability

    The previous two tutorials might have been interesting, or even enjoyable, but this particular one isn't gonna be very nice. Many programmers go for years writing non-portable programs when they could have easily written the program portably, so I'm gonna nip this one in the bud straight away and talk about how to properly write programs. If you learn this stuff from the start, you're on the right track for the rest of your C career. So, here goes.

    Portability is about being able to write a program just once, and to have it work on a variety of machines. For some programs, this means it can work on Linux, Mac, and W******. For other programs, it might mean being able to work on anything from a tiny 8-Bit microcontroller to a large 256-Bit supercomputer. In order for us to have portability, there must be some sort of "standard" by which we write our programs.

    C is a bit of a shape-shifter, a mutant. It came to be that C should work on every machine. Systems that support C are radically different from each other. Some have a 16-Bit "int", others have a 64-Bit "int", and even some have a 36-Bit "int". The C Standard gives huge freedom. Here's some of the freedoms it gives:

    1) A byte must be at least 8 bits, but there's no upper limit on its size. You can have a 9-Bit byte, or even a 64-Bit byte.
    2) For storing negative numbers, the system can use sign-magnitude, or 1's complement, or 2's complement.
    3) The byte order for storing numbers can be any order (e.g. big endian, little endian, mixed endian).

    Now the problem with C is that there are two standards. There's the standard of 1989, and the standard of 1999. The latter standard was an improvement over its predecessor, it made the following changes:
    1) It removed stupid features that only caused problems (such as implicit function declarations)
    2) It added some new features

    Now you might think that C99 should have overtaken C89... but the problem is that people were so happy with C89 that they didn't pay much attention to C99. If you write a program that conforms to the C99 standard, you can't be sure that it will compile on every machine because a lot of machines still only have C89. (Well that isn't quite true... they have C89 as well as a few more features that came with C99). C compilers today are sort of half way between C89 and C99. Some of the C99 features they support are as follows:
    1) The "inline" feature, which allows you to inject function code instead of calling a function.
    2) The "long long" integer type that's at least 64-Bit. Prior to this, the biggest integer type was "long" and it was only at least 32-Bit.
    3) The ability to define a variable anywhere within a block of code (as opposed to only at the start).

    I'm a great fan of portability, so I stick to C89... but there are a handful of C99 features that I use because I know that every compiler supports them. For instance I define a variable wherever I want in a function, reason being that it's a thousand times better than defining all the variables at the start.

    Thankfully, the whole C89 versus C99 thing works out OK, so long as you don't use some wild C99 features (such as variable-length stack arrays), your program will be portable.

    Now moving on, the first thing to consider in writing portable programs is the integer types:

    Even though C gives all sorts of wonderful freedoms, it still had to be sensible and give a few limits. First of all, I'm going to talk about the types used for storing unsigned integer numbers. Here they are, along with their minimum ranges as specified by the C standard:

    Code:
    char unsigned (0 through 255, that's 8 bits)
    short int unsigned (0 through 65535, that's 16 bits)
    int unsigned (0 through 65535, that's 16 bits)
    long int unsigned (0 through 4294967296, that's 32 bits)
    long long int unsigned (0 through 18446744073709551615, that's 64 bits)
    
    And here's another restriction:
    
    range of char <= range of short <= range of int <= range of long <= range of long long
    
    Therefore, if you know that a certain number will fit inside a short, it will definitely fit inside an int.
    First thing to note is that "int" is the default type, so you can just write "long" intead of "long int". Second thing is that the words can be in any order you like, e.g. "unsigned int short".

    In the C Standard, it says that "int" is the "natural size type for the system". Maybe this means that on a 32-Bit system, "int" will be 32-Bit. Some people speculate that this means that "int" will be the fastest integer type. When people just want to store a number, they'll typically use int. If you're a portable programmer such as myself, then the important information you need is that "int" is only guaranteed to be at least 16-Bit -- therefore do not use it to store a 32-Bit number, not in a fully-portable program.

    The smaller integer types, i.e. char and short, are typically used when you want to save memory. For instance if you want to store half a million integers in memory, you'll probably use the smallest type possible. The larger integer types are for when you want to store a bigger number, e.g. a 32-Bit number or a 64-Bit number.

    I myself like my code to run lightning fast on everything from a toaster to a supercomputer, so I don't use the built-in integer types. Instead, I include a header file called "stdint.h", and I use the special integer types defined in it. This header file contains such integer types as "uint_fast8_t", which means the fastest integer type that can store at least 8 bits. Now there's a little problem here. The header file, "stdint.h", is something new that came with C99, it's not mentioned in the C89 standard. None the less, this header file is included in the majority of compilers, but not all of them. You might at some stage encouter a compiler that doesn't have "stdint.h", but there's a simple remedy for that, there's a "one size fits all" version of "stdint.h" that works on every system.

    Here's these integer types in action:

    Code:
    #include <stdint.h>
    
    int main(void)
    {
        uint_fast8_t i = 255u;
    
        uint_fast64_t k = 872387u;
        return 0;
    }
    (I'll talk about the little "u" written after the numbers in a few minutes)

    As the name suggests, "uint_fast8_t" is the fastest integer type that is capable of storing an 8-Bit number. So on a tiny microchip, it'll probably be an 8-Bit integer type. On a supercomputer though, it might be a 128-Bit integer type. I don't care how big it is though, I just want to make sure it's at least 8 bits.

    If I wanted to store a bazillion small numbers in memory, I might use the "uint_least8_t" integer type, which means the smallest integer type that can store at least 8 bits.

    Once upon a time, before I started using the "uint_fastX_t" family of types, I used to use plain old "unsigned int" whenever I wanted to store a simple integer number, even if the number was small like 7 or 52. This is the way the vast majority of C programmers operate. If you look at the limits I gave above though for the integer types, you'll see that "unsigned int" must be at least 16-Bit. This is gonna be a problem when compiling a program for an 8-Bit microchip, the program will be only half as fast because it's using two bytes of memory where it could have used just one! If you had used "uint_fast8_t" instead of "unsigned", you'd get optimal performance on everything from a toaster to a supercomputer.

    Next thing to consider is "integer literals". A integer literal is a number written in your program, such as follows:

    Code:
    uint_fast16_t a;
    
    a = 6;
    In this little snippet, "6" is an integer literal. More precisely, it's an expression of type "signed int", and it's value is 6. Notice that it's signed as opposed to unsigned. The default in C is signed, but 9 times out of 10 you only need positive numbers. As discussed previously, the "int" type is only guaranteed to be 16-Bit, which means the greatest value you can store in a 16-Bit integer is 32767. Because of this, you do not want to write the following in your code:

    256 * 256 * 256 (the asterisk means multiply in C)

    The result of this multiplication is 16777216, which is too big for a 16-Bit number, so you can't do that in a portable program. (You'll get away with in in Linux because "int" is 32-Bit in the gcc compiler). To change the type of an integer literal, you have two choices:
    1) Write "u" and "l" after it. The "u" means unsigned, and the "l" means long. Therefore "6u" is of type "unsigned int", and "6ul" is of type "long unsigned it". (Remember you can write that as "long unsigned int", or even "long int unsigned").
    2) The other way is to perform a "cast". To perform a cast, you just put the type's name in parentheses behind the integer literal, for instance:
    (uint_fast32_t)56u

    At this early stage, my advice is to put "u" after every integer literal because we never want to work with signed integer types (not unless they're needed). So in the previous example, we would have needed:

    256ul * 256ul * 256ul

    (It's possible to leave out the "ul" on the last two, but I won't get into that now)

    My advice is avoid signed integer types like the plague, only use unsigned integer types unless you have an explicit need for negative numbers. There are two reasons why I don't like signed integer types:
    1) They can behave differently on different systems because there are three possible number systems for storing negative numbers.
    2) If you "overflow" a signed integer type, bad things happen. (I'll go into further detail on this later).

    Unsigned integer types don't have either of these problems.

    So to summarise, here's my advice on writing portable programs:
    1) Only use unsigned integer types (unless you need negative numbers)
    2) Use the integer types defined in "stdint.h"

    Now if that didn't turn your stomach, you can hit me back with an "I'm listening". Next I think I'll talk about loops.
    Ask questions on the open forums, that way everybody benefits from the solution, and everybody can be corrected when they make mistakes. Don't send me private messages asking questions that should be asked on the open forums, I won't respond. I decline all "Friend Requests".

  2. #2
    Member imported_Deathray's Avatar
    Join Date
    Oct 2007
    Posts
    381

    Default

    That was great loved it! Can't wait for the next one!
    Maybe in all of your tutorials you can link to some recommended resource's to
    continue on with the learning in between the tutorials - so we aren't just tied
    to yours (:
    - Poul Wittig

  3. #3
    Very good friend of the forum Virchanza's Avatar
    Join Date
    Jan 2010
    Posts
    863

    Default

    Quote Originally Posted by Deathray View Post
    That was great loved it! Can't wait for the next one!
    Maybe in all of your tutorials you can link to some recommended resource's to
    continue on with the learning in between the tutorials - so we aren't just tied
    to yours (:
    I'm trying to walk the tight-rope between:
    a) Making it fun and interesting
    and
    b) Actually giving you a good foundation

    The problem with a powerful programming language like C is that it has loads of gotcha's. For instance, if I didn't do a little article about "integer promotion", then I can be sure that one day you'll write a program that has a bug because of integer promotion. Learning about these things at the start isn't much fun though, so I'm trying my best not to put anyone off. So I might do one "fun" article, and then one "you need to know this" article.

    Next I think I'll do loops, and then afterwards I'll do integer promotion.

    As regards other resources, well BE CAREFUL (I rarely write in all caps!). 95% of the stuff you'll find on the net about C is complete crap.

    If you want a reference manual for the C standard library, then here's the one and only: http://www.dinkumware.com/manuals/default.aspx

    Give it a try, type "strcpy" into the search box and hit Return!
    Ask questions on the open forums, that way everybody benefits from the solution, and everybody can be corrected when they make mistakes. Don't send me private messages asking questions that should be asked on the open forums, I won't respond. I decline all "Friend Requests".

  4. #4
    Good friend of the forums
    Join Date
    Jun 2008
    Posts
    425

    Default

    Thanks Virchanza
    I've run into a problem, and can't seemed to find a example how to fix it.
    I've got a packet_buff, that has the whole packet(mac/ip/tcp/data), which is sniffed of the network, i'm trying to then resend it out a different interface. I've got both send and recive sorted, but to link it is the probelm. I would like to do something like this.

    ip_header->ip_dest = packet_buf[110] + packet_buf[111] ....... etc
    then
    send(raw,ip_header);

    The first line creates bad data,The packet_buf is a unsigned char, and ip_header is struct iphdr * , I can make it display the info from packet_buf like printf("Dest Mac %x,%x,%x,%x,%x,%x",packet_buf[0].....packet_buf[5]);, but if i can't put it into a struct to then change the data like ip address and syn/ack and data, it won't work as planned.

    Thanks

    Don't know wither this is accurate, but i++; is slower than i = i+1;

  5. #5
    Very good friend of the forum Virchanza's Avatar
    Join Date
    Jan 2010
    Posts
    863

    Default

    The first line creates bad data,The packet_buf is a unsigned char, and ip_header is struct iphdr * , I can make it display the info from packet_buf like printf("Dest Mac %x,%x,%x,%x,%x,%x",packet_buf[0].....packet_buf[5]);, but if i can't put it into a struct to then change the data like ip address and syn/ack and data, it won't work as planned
    Don't bother creating an "struct iphdr" object and copying the data across. Instead, fool the compiler into thinking that your array of unsigned char is actually an iphdr. As follows:

    (struct iphdr*)packet_buf

    packet_buf is of the type "char unsigned [N]", but arrays will decay to a pointer to their first element, so you have "char unsigned *". What the cast does is take the "char unsigned *" and pretend that it's a "struct iphdr *". Give it a try.

    By the way, have you taken padding into account? You must make sure that there is no padding in your "iphdr" structure. There's no portable way of making sure of this but there are GCC compiler extensions that let you do it.

    Don't know wither this is accurate, but i++; is slower than i = i+1;
    That can be true in C++ when dealing with elaborate user-defined types, but not so in C. I myself use "++i" because it accurately reflects the behaviour of "i += 1", and also because it's a nice habit to have for when I'm writing in C++.
    Ask questions on the open forums, that way everybody benefits from the solution, and everybody can be corrected when they make mistakes. Don't send me private messages asking questions that should be asked on the open forums, I won't respond. I decline all "Friend Requests".

  6. #6
    Good friend of the forums
    Join Date
    Jun 2008
    Posts
    425

    Default

    Don't bother creating an "struct iphdr" object and copying the data across. Instead, fool the compiler into thinking that your array of unsigned char is actually an iphdr. As follows:

    (struct iphdr*)packet_buf

    packet_buf is of the type "char unsigned [N]", but arrays will decay to a pointer to their first element, so you have "char unsigned *". What the cast does is take the "char unsigned *" and pretend that it's a "struct iphdr *". Give it a try.

    By the way, have you taken padding into account? You must make sure that there is no padding in your "iphdr" structure. There's no portable way of making sure of this but there are GCC compiler extensions that let you do it.
    Thank you. I tryed the above but couldn't get it to work(proable because i don't understand it,doing some more google). I did the cheat way and just send the packet from read to write, but i'm still going to have to read the packet data some how as i would like to change stuff like ip address before its forwarded. My idea about putting it into the ip/mac/tcp header struct was becusae i thought it would automacticly sort out the what data was for what feild, i was proable wrong.
    The padding thing you mentioned I have seen it in pages on the internet about headers, but don't know about gcc options. Would that corupt the packet as a whole insome way by setting the flag(gcc). Is there a way from reading the packet to work out the padding.
    If i could find what number in the packet buffer the ip source address was and did some thing like
    char * store
    read(packet_buf)
    store=ntohs(packet_buf)
    printf("%s",store);

    or

    char newip[19] = 127.0.0.1
    packet_buf=htons(newip)
    send(packet_buf)

    If you know a area i can google, it will be welcomed
    Cheers

    edit
    Just tryed some more ways what you posted.
    struct ethhdr *eth;
    eth=(struct ethhdr *)packetbuf
    It works displaying the data. But if i do the same thing with ip+sizeof(ethhdr)
    printf("%s\n",inet_ntoa(ip_header->saddr);
    It say incomplete type.
    Hope you can understand my grammer.

  7. #7
    Very good friend of the forum Virchanza's Avatar
    Join Date
    Jan 2010
    Posts
    863

    Default

    Quote Originally Posted by compaq View Post
    My idea about putting it into the ip/mac/tcp header struct was becusae i thought it would automacticly sort out the what data was for what feild, i was proable wrong.
    You're not wrong, you're right. But you just have to make sure that "iphdr" doesn't contain any padding. By the way, if you're sniffing packets, should you not be working with an ethernet header?

    The padding thing you mentioned I have seen it in pages on the internet about headers, but don't know about gcc options. Would that corupt the packet as a whole insome way by setting the flag(gcc). Is there a way from reading the packet to work out the padding.
    Using gcc at the command line, you just specify the option "-fpack-struct" and then you know there'll be no padding in your structs. (There's another way of doing it on a struct-by-struct basis, something to do with "__attribute__" or something like that).

    Just tryed some more ways what you posted.
    struct ethhdr *eth;
    eth=(struct ethhdr *)packetbuf
    There you go, that should do the trick.

    It works displaying the data. But if i do the same thing with ip+sizeof(ethhdr)
    printf("%s\n",inet_ntoa(ip_header->saddr);
    It say incomplete type.
    Hope you can understand my grammer.
    Let's say you have header structures as follows:

    Code:
    #include <stdint.h>
    
    struct Header_Ethernet {
        uint8_t dst[6], src[6];
        uint16_t proto;
    };
    
    struct Header_IP {
        uint32_t src, dst;  /* Yes I realise this isn't how an IP header looks */
    };
    Now, once you read the frame in, all you have to do is cast the pointer to the appropriate header pointer type:

    Code:
    char unsigned *p_frame = ReadEthernetFrame();
    ((Header_Ethernet*)p_frame).dst[3] = 0x33;
    p += sizeof(Header_Ethernet);
    ((Header_IP*)p_frame).dst = 24234;
    Ask questions on the open forums, that way everybody benefits from the solution, and everybody can be corrected when they make mistakes. Don't send me private messages asking questions that should be asked on the open forums, I won't respond. I decline all "Friend Requests".

  8. #8
    Good friend of the forums
    Join Date
    Jun 2008
    Posts
    425

    Thumbs up

    Cheers dude for point me in the right direction
    #include <stdint.h>

    struct Header_Ethernet {
    uint8_t dst[6], src[6];
    uint16_t proto;
    };

    struct Header_IP {
    uint32_t src, dst; /* Yes I realise this isn't how an IP header looks */
    };Now, once you read the frame in, all you have to do is cast the pointer to the appropriate header pointer type:


    Code:
    char unsigned *p_frame = ReadEthernetFrame();
    ((Header_Ethernet*)p_frame).dst[3] = 0x33;
    p += sizeof(Header_Ethernet);
    ((Header_IP*)p_frame).dst = 24234;
    I will have a look

  9. #9
    Very good friend of the forum Virchanza's Avatar
    Join Date
    Jan 2010
    Posts
    863

    Default

    In my Internet Prober program, I use a totally different method of going about this. Instead of using structs, I use offsets. For instance, I might have:

    #define OFFSET_ETH_PROTO 8u /* Protocol in Ethernet header */

    and then I'd set it as follows:

    char unsigned buf[whatever_size];

    Set16( buf+OFFSET_ETH_PROTO, whatever_value );

    This helps greatly with portability.
    Ask questions on the open forums, that way everybody benefits from the solution, and everybody can be corrected when they make mistakes. Don't send me private messages asking questions that should be asked on the open forums, I won't respond. I decline all "Friend Requests".

  10. #10
    Good friend of the forums
    Join Date
    Jun 2008
    Posts
    425

    Default

    --------------------------------------------------------------------------------

    In my Internet Prober program, I use a totally different method of going about this. Instead of using structs, I use offsets. For instance, I might have:

    #define OFFSET_ETH_PROTO 8u /* Protocol in Ethernet header */

    and then I'd set it as follows:

    char unsigned buf[whatever_size];

    Set16( buf+OFFSET_ETH_PROTO, whatever_value );

    This helps greatly with portability.
    Just to see if I understand, buf will get broken up into 8u and the whatever value is like 100bit. If so to read the source ip address I would do something like
    Set16(buf+8u(eth)+4u(ip i think),92(start of source in the ip packet)
    ??

    Would you beable to give me a couple of lines of code like source ip and how convert the info to string.

    The ip source address i was trying to read didn't compile correctly because i was using struct iphdr *ip instead of struct sockadd_in *in, but even that puts out bad data.

    Cheers

Page 1 of 2 12 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •