I like to think I know C well enough. I’ve read K&R cover to cover, and I’ve done my fair share of C hacking over the years. But I’ve learned that with C, you have to be prepared, always, and at all times, to eat humble pie. I feel compelled to share this whopper of a slice that came steaming hot from Van Der Linden – which I’ve now determined should be required reading for anyone even thinking about ever going near C.

#include "stdio.h"

int main(int argc, char *argv[]) {
    unsigned int x = 1;
    if(-1 < x)
        printf("yes\n");
    else
        printf("no\n");
    return 0;
}

Simple enough? WRONG. When a binary operation is invoked on two incompatible types (in this case, an unsigned int and an int), the “usual arithmetic conversions” apply.

Time to put on that language lawyer hat. According to the C99 rules:

If the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type is converted to the type of the operand with unsigned integer type.

Rank?

The rank of any unsigned integer type is equal to the rank of the corresponding signed integer type, if any.

Which means, of course, that -1 is “promoted”, at least on the x86-64 platform. And you can be damn sure that 0xFFFFFFFF when considered 4,294,967,295 decimal, is greater than one. Now imagine that your unsigned int definition lives in some other translation unit, long, long ago and far, far away. No, -Wall will not save you.

Another psychopathic killer: automatic string concatenation. Take the following code:

#include "stdio.h"

int main(int argc, char *argv[]) {
    char *config[] = {
        "a man ",
        "a plan ",
        "a canal "
        "panama ",
    };
 
    printf("%lu\n", sizeof(config)/sizeof(config[0]));
    printf("%s\n", config[2]);

    return 0;
}

Do you see the bug? I’ve misplaced the comma, which should come after “a canal ” instead of “panama “. Don’t let that final comma fool you: the C compiler just ignores it. However, the C compiler is just jonesing to remove that NUL terminating char ‘\0’ from ‘a canal \0’ and append ‘panama \0’, in which case you’ve just wound up with three, not four, config parameters; and with ‘a canal panama\0’ as your third config entry.

Happy bug hunting!

My friend was kind enough to give me a 64 gig solid-state disk drive to throw into my development box. I was curious about hard disk speed compared to what I’ve been running: a four-disk RAID5 array of Seagate Barracuda 7200 320GB 7200 RPM SATA (3.0Gb/s) on an Areca ARC-1210 PCI-Express x8 SATA II (3.0Gb/s) Controller Card. Just checked my Newegg order history: the two hard drives are from January 2006, the other two from Sept 2006. I didn’t know they had gotten so old! Haven’t had any failures yet, though.

A few tests (sdb5 = raid5, sda5 = sdd):

sudo hdparm -tT /dev/sdb5

/dev/sdb5:
 Timing cached reads:   15534 MB in  1.99 seconds = 7788.19 MB/sec
 Timing buffered disk reads: 456 MB in  3.01 seconds = 151.39 MB/sec

sudo hdparm -tT /dev/sda5

/dev/sda5:
 Timing cached reads:   14776 MB in  1.99 seconds = 7407.07 MB/sec
 Timing buffered disk reads: 558 MB in  3.00 seconds = 185.80 MB/sec

Interesting: the cached throughput on the RAID controller is very good, beating my motherboard controller by a sound 400MB/sec. The reads on the SDD are faster by about 30MB/sec. What about random access seek times? I found some C code to do random seeks. Save the code as seeker.c, and compile with gcc seeker.c -o seeker.

#define _LARGEFILE64_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>

#define BLOCKSIZE 4096
#define TIMEOUT 30

int count;
time_t start;

void done()
{
    time_t end;

    time(&end);

    if (end < start + TIMEOUT) {
        printf(".");
        alarm(1);
        return;
    }

    if (count) {
      printf(".\nResults: %d seeks/second, %.2f ms random access time\n",
         count / TIMEOUT, 1000.0 * TIMEOUT / count);
    }
    exit(EXIT_SUCCESS);
}

void handle(const char *string, int error)
{
    if (error) {
        perror(string);
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char **argv)
{
    char buffer[BLOCKSIZE];
    int fd, retval;
    unsigned long numblocks;
    off64_t offset;

    setvbuf(stdout, NULL, _IONBF, 0);

    printf("Seeker v2.0, 2007-01-15, "
           "http://www.linuxinsight.com/how_fast_is_your_disk.html\n");

    if (argc != 2) {
        printf("Usage: seeker <raw disk device>\n");
        exit(EXIT_SUCCESS);
    }

    fd = open(argv[1], O_RDONLY);
    handle("open", fd < 0);

    retval = ioctl(fd, BLKGETSIZE, &numblocks);
    handle("ioctl", retval == -1);
    printf("Benchmarking %s [%luMB], wait %d seconds",
           argv[1], numblocks / 2048, TIMEOUT);

    time(&start);
    srand(start);
    signal(SIGALRM, &done);
    alarm(1);

    for (;;) {
        offset = (off64_t) numblocks * random() / RAND_MAX;
        retval = lseek64(fd, BLOCKSIZE * offset, SEEK_SET);
        handle("lseek64", retval == (off64_t) -1);
        retval = read(fd, buffer, BLOCKSIZE);
        handle("read", retval < 0);
        count++;
    }
    /* notreached */
}

You need an accurate blocksize; for me, that was 4096.

$ sudo dumpe2fs /dev/sdb5 | grep 'Block size'
dumpe2fs 1.41.14 (22-Dec-2010)
Block size:               4096

So the results?

RAID5:

Seeker v2.0, 2007-01-15, http://www.linuxinsight.com/how_fast_is_your_disk.html
Benchmarking /dev/sdb5 [420909MB], wait 30 seconds..............................
Results: 85 seeks/second, 11.73 ms random access time

SDD:

Seeker v2.0, 2007-01-15, http://www.linuxinsight.com/how_fast_is_your_disk.html
Benchmarking /dev/sda5 [27235MB], wait 30 seconds..............................
Results: 5429 seeks/second, 0.18 ms random access time

The SSD is two orders of magnitude improvement over the RAID5. Not surprising, but still pretty awesome.

Finally, I wanted to migrate my /home directory onto the SSD. This process worked for me:

sudo mkdir /mnt/ocz
sudo mount /dev/sda5 /mnt/ocz
sudo find . -depth -print0 | sudo cpio --null --sparse -pvd /mnt/ocz
sudo mv /home /home_old
sudo mkdir /home
sudo mount /dev/sda5 /home

Don’t forget to add the mount to /etc/fstab so it stays there! Best to use the UUID of the device, so:

11:29 ~ $ sudo blkid
[sudo] password for adam:
/dev/sda5: LABEL="Linux_OCZ" UUID="..." TYPE="ext4"

So /etc/fstab gets another entry, e.g.:

# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
# /home => ocz ssd disk
UUID=...        /home           ext4    errors=remount-ro 0       1
© 2014 Adam Klein's Blog Suffusion theme by Sayontan Sinha, modified by Adam :)