Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Last active August 29, 2015 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshCheek/a0a4eb24b69386ce7995 to your computer and use it in GitHub Desktop.
Save JoshCheek/a0a4eb24b69386ce7995 to your computer and use it in GitHub Desktop.
C with Alexandra

Compiling a C program

A common compiler is gcc, which is the Gnu C Compiler. On a mac, it's actually a different compiler called clang. You have this if you installed homebrew, because it needs to compile programs, so it installs Clang for you :)

$ ls -l
total 32
-rw-r--r--  1 josh  staff  3800 May 21 13:25 Readme.md
-rw-r--r--  1 josh  staff   613 May 21 11:18 binary.c
-rw-r--r--  1 josh  staff   591 May 21 11:30 howbig_are_things.c
-rw-r--r--  1 josh  staff  2382 May 21 12:23 practice.c

$ gcc binary.c # compiles the program, gives it the default name a.out

$ ls -l
total 56
-rw-r--r--  1 josh  staff  3800 May 21 13:25 Readme.md
-rwxr-xr-x  1 josh  staff  8724 May 21 13:26 a.out
-rw-r--r--  1 josh  staff   613 May 21 11:18 binary.c
-rw-r--r--  1 josh  staff   591 May 21 11:30 howbig_are_things.c
-rw-r--r--  1 josh  staff  2382 May 21 12:23 practice.c

$ ./a.out # run the program
-129 - 01111111
-128 - 10000000
...

Executable programs

I made an elective that goes into how these things work. Here's the bit about permissions. Here's the bit about locating programs you're trying to run by setting an environment variable called PATH

Representing values

Unsigned

# 1 byte = 8 bits

0b00000000  # => 0
0b11111111  # => 255

Signed:

0b1... means negative (leading bit is set) 0b0... means positive (leading bit is not set)

Then the rest of the number is treated as normal.

This is almost true, except negative numbers have a slightly different representation (called 2s complement), for the purpose of making addition cheap (try adding the bits for 2 and -1, which you can see below).

Rollover

Because we only have 8 bits, and 1 is reserved for the sign, we can only represent numbers -128 through 127.

We wrote the program binary.c (included) to see where they rollover, and how they are represented.

-129: 01111111 <-- rollover, this is +127
-128: 10000000 <-- smallest negative number
-001: 11111111
0000: 00000000 <-- 0 is neither positive nor negative,
                   its value starts with 0, though,
                   so 1 fewer positive numbers than negative
0001: 00000001 <-- counting like "normal"
0127: 01111111 <-- largest positive number
0128: 10000000 <-- rollover, this is -128

How Ruby deals with these constraints

Ruby has 64 bits it can use to represent a number (because I'm on a 64 bit machine).

It uses 1 bit for the sign, and one bit for some other purpose (pretty sure I know, if you're ever curious ^_^). So, there are 2^62 positive values and 2^62 negative values.

Since the number 0 occupies the positive space, our maximum positive number is one fewer than that.

Once we rollover, Ruby switches to an alternate representation.

# available_bits           = 64
# bits_used_for_other_shit = 2
# values_used_for_0        = 1
2**(64-2)-1).class # => Fixnum
2**(64-2)  ).class # => Bignum

How to identify whether a bit is set:

The & will do a "bitwise" comparison, meaning it will give you a new number, where each bit is set if that bit is set on both inputs

For 3

logically:

3 has binary "011"
& with 1:    "001" gives "001"
& with 2:    "010" gives "010"
& with 4:    "100" gives "000"

See it in Ruby

3 & 1  # => 1
3 & 2  # => 2
3 & 4  # => 0

For 7

7 has binary value of "0111"

7 & 1  # => 1
7 & 2  # => 2
7 & 4  # => 4
7 & 8  # => 0

Pointers

Other representations of strings

Ascii has no values larger than 127, so no ascii values will have the first bit set.

Utf8 supports ascii because if the first bit is a 0, then it maps to the ascii values. But if the first bit is a 1, then it means the character requires more than one byte to represent. So all the fancy characters, and characters from other languages, are represented by setting the first bit, and then having another system of mapping the bits to numbers.

string = "a∂"    # => "a∂"
string.encoding  # => #<Encoding:UTF-8>
string.chars     # => ["a", "∂"]
string.bytes     # => [97, 226, 136, 130]

"a".ord  # => 97
97.chr   # => "a"
#include <stdio.h>
// See the Readme for an explanation of why this is :)
void show_bits(int i, char* bitstring) {
bitstring[8] = 0;
bitstring[7] = (i&1) ? '1' : '0';
bitstring[6] = (i&2) ? '1' : '0';
bitstring[5] = (i&4) ? '1' : '0';
bitstring[4] = (i&8) ? '1' : '0';
bitstring[3] = (i&16) ? '1' : '0';
bitstring[2] = (i&32) ? '1' : '0';
bitstring[1] = (i&64) ? '1' : '0';
bitstring[0] = (i&128) ? '1' : '0';
}
int main() {
char bitstring[9] = {};
int i;
for(i = -129; i < 129; ++i) {
show_bits(i, bitstring);
printf("%04d - %s\n", i, bitstring);
}
return 0;
}
#include <stdio.h>
int main() {
// 1 byte = 8 bits
printf("int: %d bytes\n", (int)sizeof(int));
printf("unsigned int: %d bytes\n", (int)sizeof(unsigned int));
printf("char: %d bytes\n", (int)sizeof(char));
printf("unsigned char: %d bytes\n", (int)sizeof(unsigned char));
printf("char*: %d bytes\n", (int)sizeof(char*));
printf("int*: %d bytes\n", (int)sizeof(int*));
return 0;
}
/*
int: 4 bytes
unsigned int: 4 bytes
char: 1 bytes
unsigned char: 1 bytes
char*: 8 bytes
int*: 8 bytes
*/
#include <stdio.h>
int main() {
char* greeting = "Hello, world";
printf("Greeting: %s\n", greeting);
// pointer arithmetic
printf("greeting+1: %s\n", greeting+1);
// the bytes have the values 1, 2, 3, 4
// 00000001 00000011
// 00000010 00000100
int n = 16909060;
int* num_ptr = &n; // num_ptr is pointing at the address of n
printf("If I go to the address of num_ptr, I get: %d\n", *num_ptr);
char* char_ptr = (char*)num_ptr;
printf("If I go to the address of the number, and look at each byte: %d, %d, %d, %d\n",
(int)*(char_ptr+0),
(int)*(char_ptr+1),
(int)*(char_ptr+2),
(int)*(char_ptr+3)
);
// 'claw'.chars # => ["c", "l", "a", "w"]
// .map(&:ord) # => [99, 108, 97, 119]
// .map { |n| '%08b' % n } # => ["01100011", "01101100", "01100001", "01110111"]
// .join # => "01100011011011000110000101110111"
// .to_i(2) # => 1668047223
n = 1668047223;
char_ptr = (char*)(&n);
// look at 1 byte at a time, pretend the bits are characters
printf("----- As characters -----\n");
printf("*(char_ptr+0): %c\n", *(char_ptr+0));
printf("*(char_ptr+1): %c\n", *(char_ptr+1));
printf("*(char_ptr+2): %c\n", *(char_ptr+2));
printf("*(char_ptr+3): %c\n", *(char_ptr+3));
// look at 1 byte at a time, pretend the bits are numbers
printf("----- As numbers -----\n");
printf("*(char_ptr+0): %d\n", (int)*(char_ptr+0));
printf("*(char_ptr+1): %d\n", (int)*(char_ptr+1));
printf("*(char_ptr+2): %d\n", (int)*(char_ptr+2));
printf("*(char_ptr+3): %d\n", (int)*(char_ptr+3));
printf("----- Pointer play ^_^ -----\n");
int x = 700;
int y = 743;
num_ptr = &x;
printf("The value of num_ptr: %d\n", (int)num_ptr);
printf("The address of x: %d\n", (int)&x);
printf("The address of y: %d\n", (int)&y);
printf("The value at the address of num_ptr: %d\n", (int)*num_ptr);
printf("The value of x: %d\n", (int)x);
printf("The value of y: %d\n", (int)y);
printf("The value of x: %d\n", x);
*num_ptr = 100;
printf("The value of x: %d\n", x);
printf("The value of y: %d\n", y);
*(num_ptr-1) = 200;
printf("The value of y: %d\n", y);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment