CSCI 241 - Homework 4: Bits are bits
Due by 11:59.59pm Monday March 13, 2017
In this lab you will get experience dealing with data on the binary level.
You will also have to deal with static local variables and using global
variables. In addition, you'll be expected to use conditional compilation
via a Makefile, creating header files, etc.
You may work with a partner on this assignment. If you choose to do so,
it is expected that you work together and equally contribute to the
development of your solution. Also, you are both responsible for understanding
how your solution works. You need only submit one assignment per group, but
clearly indicate your partnership in the README and comments for files.
Part 1 - converting data to binary and back
In this part, you will be creating 2 programs. encode_bits which
will generate the "binary" representation of a file and decode_bits
which will take that representation and convert it back to the original
Create a program called encode_bits. This program should use
getchar() to read in characters one at a time and then call
print_bits() (see below) to output that
character as a sequence of '1' and '0' characters. It should stop on EOF.
Create a program called decode_bits. This program should use
getchar() to read in characters one at a time and then call
decode_bits() (see below) to output that
sequence of '1' and '0' characters as actual characters. It should stop on
bits.c & bits.h
Create a file called bits.c that contains the following two
functions and a header file bits.h that contains a guard against
multiple inclusion, other needed includes, and function prototypes.
- void print_bits(int ch)
- Takes the character ch and outputs its value in binary format
with all leading zeros and with the MSB first. For example, the letter 'A'
has a value of 0x41, and should be output as 01000001. A newline
character has a value of 0x0a and should be output as 00001010 .
- You should not assume the number of bits that are in a char, instead use
the constant CHAR_BIT from limits.h.
- void decode_bits(int ch)
- If the character is whitespace, skip it. (You might want to use
isspace() in ctype.h.)
- If the character is a '1' or a '0', you should add it into an output
buffer (numerically, shifting the current contents appropriately).
Once you've seen CHAR_BIT bits, you should print the corresponding
- If the character is neither white space, nor a binary digit, you should
print a message to the screen, and then use exit() to quit your
program with a non-zero value.
Programming hints for part 1
You'll probably need to use static local variables to handle
decode_bits since it only prints out a character every CHAR_BIT
calls to it.
Don't forget to make rules in your Makefile include the correct
compilation. bits.o should be the dependency for the two other
programs, and you should have a separate rule for its compilation.
Part 2 - Number Transformation
For the second part, you will be creating a function to read in a signed
integer value and storing the result in a long integer variable.
You will also be creating 4 short programs that will use that function to
read in integers and output them in one of 4 different formats -- binary,
decimal, octal, or hexadecimal.
reading in a number
Create a file called getnum.c and another called
getnum.h. In getnum.c you will create the function
getnum() that is used by your other programs.
- long getnum(void)
- Read in a signed integer in one of 4 formats described below and then
return the value.
- Skip leading whitespace and stop reading in a number on the next
occurrence of whitespace.
- All formats optionally begin with a - to indicate negative
value. Positive values do not have this (i.e., no '+' for positive).
- Binary - begins with a leading 0b and then a
sequence of 0 and 1 characters
- Octal - begins with a leading 0 followed by 1 or
more digits from 0-7
- Decimal - either a single 0 or a digit from 1-9
followed by zero or more digits from 0-9
- Hexadecimal - begins with a leading 0x followed by 1
or more digits from 0-9 or A-F
- If the integer read is invalid, somehow signal the caller that the value
returned is not valid. I'd suggest you consider a global variable.
- If the input is invalid, skip to the next whitespace in the input or EOF.
You might find it useful to "unread" a character.
You can do so using ungetc(ch, stdin); where "ch" is the character
you just read. Note that you can only un-read one character at a time
until you read in a new character.
- Building a state diagram like the one below might be useful in
visualizing the behavior of this function.
Printing out a number
You will then create 4 short programs that will read in a sequence of number
and then output them one per line in a specified format. All 4 will be
using sign-magnitude format. (If negative, print out the sign and then the
rest as if it were positive -- important for binary.)
- tobinary - outputs the signed value in binary with a leading 0b
- todecimal - outputs the signed value in base 10 with no leading 0 characters
- tooctal - outputs the signed value in base 8 with a single leading 0
- tohex - outputs the signed value in base 16 (uppercase) with a leading 0x
In the event that the integer being read is invalid, simply print
"ERROR" to the screen.
Loop until no more integers remain.
Programming hints for part 2
- You will probably want to have more functions than just the one required in this
part. You can also have other functions or global flags that can be used by
the toBASENAME programs.
- You may ignore the possibility of an integer overflow in the numbers you read.
- At no point in time should your program segfault or generate errors when running under valgrind.
- Be sure your program can handle both upper and lower case in hex.
("man ctype" for inspiration)
- Be sure to handle the special case of single '0' character.
- Be sure to handle the case where EOF immediately follows a valid integer.
Don't forget to add these targets into your Makefile.
The rest of the info
I've included my sample solution in ~rhoyle/pub/cs241/hw04/ with
binaries that should work on the lab machines.
decode_bits should exactly undo encode_bits. For example,
you should get no output from the following:
% ./encode_bits < ./encode_bits | ./decode_bits > output
% diff -q encode_bits output
You can also chain together the base transformation if you like
% echo -32767 0xffff 071 0b101 cat | ./tobinary | ./tohex | ./tooctal | ./todecimal
Read in a flag on the command line that specifies the base for the output. It should be one of:
- -b: binary (the default, as specified above)
- -o: octal (convert all 3-bit strings into a number from 0-7)
- -h: hex (convert all 4-bit strings into a number from 0-F)
Note, your output, if octal, should be the regular number without the leading 0. The number, if hex, should be the regular number without the leading 0x. You will need to modify both encode_bits and decote_bits for full credit, as you'll need to be able to decode each type of encoding.
As with the first project, I want you to create a file called README
- Your name (and partner's name) and the date
- A list of the programs with a short one-line description of each
- A description of how someone should use your version of
getnum() including a description of how it signals the validity of
the value read.
- A list of all remaining compilation problems, warnings, or errors.
Note that for full marks, it is expected that you will have corrected
all of these things.
- A statement about any valgrind errors which occur
- An estimate of the amount of time you spent designing, implementing, and
deubgging these programs
- The honor code statement:
I have adhered to the Honor Code in this assignment
Now you should make clean to get rid of your executables and
handin your folder containing your source files, README, and Makefile.
% cd ~/cs241
% handin -c 241 -a 4 hw4
Here is what I am looking for in this assignment:
- A working Makefile with your programs, all, and clean as targets
- All programs should have comments including your name and date at the
top. Functions should have a brief description of what they do and an
explanation of their return values.
- All programs should compile using gccx on clyde
with no compiler warnings or errors.
- All programs should run using valgrind on clyde
with no warnings or errors.
Last Modified: Mar 2, 2017 - Roberto Hoyle, with material from Ben Kuperman