CS360 Lecture Notes -- The Jval Type


The Jval type

In jval.h, I define a type called a Jval. This is a big union:

typedef union {
    int i;
    long l;
    float f;
    double d;
    void *v;
    char *s;
    char c;
    unsigned char uc;
    short sh;
    unsigned short ush;
    unsigned int ui;
    int iarray[2];
    float farray[2];
    char carray[8];
    unsigned char ucarray[8];
  } Jval;  

In case you don't know, a union is like a struct, except you can only use one of the fields at a time. In other words, if you have a Jval named jv, then you may set and use jv.i as an integer, and jv.d as a double, but not both at the same time.

I've made use of Jval's when I write generic data structures such as lists, and trees. For the purposes of this class, don't worry about most of the fields in the union. The only important ones are:

    int i;
    float f;
    double d;
    void *v;
    char *s;
    char c;
The nice thing about a Jval is that you can hold one piece of data in a Jval, regardless of what the type of that piece of data is. Moreover, the Jval will always be 8 bytes.

Here's a very simple example, which is pretty unmotivated, except that it demonstrates the union (src/simple_jval.c):

/* This program shows the very simple use of a jval. */

#include <stdio.h>
#include <stdlib.h>
#include "jval.h"

int main()
{
  Jval jv;

  /* Set and use as an integer. */

  jv.i = 10;
  printf("Using the jval as an integer: %d\n", jv.i);

  /* Set and use as a double. */

  jv.d = 5.6;
  printf("Using the jval as a double: %.2lf\n", jv.d);

  /* Set and use as a char *. */

  jv.s = "Fred";
  printf("Using the jval as a char *: %s\n", jv.s);

  /* Since I've set it as a (char *), I shouldn't use it as a double. */

  printf("If I now print the jval as a double, I get garbage: %lg\n", jv.d);
  return 0;
}

Pay attention to that last printf() -- you should only use a jval using the type that you used when you set its value. That last printf() is treating the 8 bytes that were set to be a pointer as a double, and accordingly you get a really weird number:

UNIX> bin/simple_jval
Using the jval as an integer: 10
Using the jval as a double: 5.60
Using the jval as a char *: Fred
If I now print the jval as a double, I get garbage: 2.13063e-314
UNIX> 

Constructor functions

You can of course, create and use a Jval by simply declaring and using it. For example:
  Jval j;
  
  j.i = 4;
You can freely pass Jval's to and from procedure calls. A Jval can be the return value of a procedure call.

Jval.h defines a whole bunch of prototypes for "constructor functions":

extern Jval new_jval_i(int);
extern Jval new_jval_f(float);
extern Jval new_jval_d(double);
extern Jval new_jval_v(void *);
extern Jval new_jval_s(char *);
These return Jval's to you when you give them arguments of a specific type. For example, if you want to initialize a Jval so that it is in integer whose value is 4, you can do it as shown above, or you can do:
  Jval j;

  j = new_jval_i(4);
Now j.i will be the integer 4. This is convenient when you are using a procedure that takes a Jval as a parameter, because you can call the procedure and never actually declare the Jval. Here's an example, in src/jval_ex2.c:

/* A program that demonstrates the "constructor" functions, and how they help
   when you are using a procedure that takes a jval as a parameter. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fields.h"
#include "jval.h"

/* Print a jval as an integer, double or string. */

void print_jval(Jval jv, char how)
{
  switch (how) {
    case 'i': printf("%d\n", jv.i); break;
    case 'd': printf("%lg\n", jv.d); break;
    case 's': printf("%s\n", jv.s); break;
    default: fprintf(stderr, "Bad 'how' variable in print_jval: %c\n", how);
             exit(1);
  }
}

/* Call print_jval.  Note that I never have to declare a jval, because I'm using the constructors. */
int main()
{
  print_jval(new_jval_i(10), 'i');
  print_jval(new_jval_d(5.6), 'd');
  print_jval(new_jval_s("Fred"), 's');
  return 0;
}

UNIX> bin/jval_ex2
10
5.6
Fred
UNIX> 

Abusing Jval's

The purpose of the Jval type is to make general purpose data structures such as dllists and red-black trees as flexible and efficient as possible. You should not use Jval's in your code for any other reason.

Specifically, you are not to say, use a Jval instead of an int in your code just because it works. That makes your code unreadable, and unreadability is majorly bad. Here is an example of bad code to average all of the integers on standard input (src/badavg.c):

/* A program that abuses jvals.  It averages integers on standard input. */

#include <stdio.h>
#include <stdlib.h>
#include "jval.h"

int main()
{
  Jval total;      /* You'll note, no types other than Jvals. */
  Jval j;
  Jval n;

  n.i = 0;         /* n and j are always treated as ints.  They should be ints. */
  total.i = 0;     /* Total starts as an int */
  
  while (scanf("%d", &(j.i)) == 1) {
    total.i += j.i;
    n.i++;
  }

  /* And then total is used as a double.  You should simply use a double.... */

  total.d = ((double) total.i) / ((double) n.i);
  printf("Average = %lf\n", total.d);
  return 0;
}

Yes, it works, and yes, it's a cute way to use total as both an int and a double. But it is revolting -- every use of Jval's is bad, and if you use them in ways like these, you should be punished.

(In case you care, the code should look as in src/goodavg.c):

/* The proper version of this program just uses doubles for total and n.  It's not
   like there are ints that can't be stored in a double, and you're using floating
   point division, so you need the doubles. */

#include <stdio.h>
#include <stdlib.h>

int main()
{
  double total;
  double n;
  int j;

  n = 0;
  total = 0;
  
  while (scanf("%d", &j) == 1) {
    total += j;
    n++;
  }

  if (n == 0) exit(1);
  printf("Average = %lf\n", total/n);

  return 0;
}


Accessor functions

I have put accessor functions into jval.h/jval.c. An accessor function simply lets you get the desired value out of a Jval by calling a function rather than accessing the field. Why would you want to do this? Well, like the constructor functions, it makes life easier in certain circumstances (when you are using a Jval that is returned from a procedure. The accessor functions are:
extern int    jval_i(Jval);
extern long   jval_l(Jval);
extern float  jval_f(Jval);
extern double jval_d(Jval);
extern void  *jval_v(Jval);
extern char  *jval_s(Jval);
extern char   jval_c(Jval);
...
So, for example, calling jval_i(j) is the same as using j.i.