// How to define an XFREE(p) macro?

// Note normally #defines go outside functions, but nothing says they can't
// be inside.

main()
{
  void *ptr = malloc(10);
  // use it
  free(ptr); // problem: doesn't null out the ptr itself
  ptr = NULL; // nice to do, so NO ONE can use this 'ptr' later on

  // desire: can we have a single call or macro that would free() + nullify
  // the pointer? Better: if our code is safe then we'd like to turn off the
  // ptr=NULL, so as to speed up the program.  Would be nice to have
  // something like:
  XFREE(ptr);

#define XFREE1(x)   free(x);x=NULL
  XFREE1(ptr); // works
  if (cond)
    XFREE1(ptr); // bad: would translate into
  if (cond)
    free(ptr);ptr=NULL;
  // which is the same as
  if (cond)
    free(ptr); // only takes a single statement
  ptr=NULL; // bad b/c likely memleak
  // instead, will need to do this
  if (cond) { // will work, b/c both free and ptr=NULL execute only if cond
	      // is true.
    XFREE(ptr);
  }

  // 2: place the {} around macro
#define XFREE2(x)   {free(x);x=NULL}
  XFREE2(ptr); // problem, no ';' at end of x=NULL
#define XFREE3(x)   {free(x);x=NULL;}
  XFREE3(ptr); // extra ';' after a '}' -- syntax error again

  // solution: use a "do .. while(0)" loop.
  // reminder: a "do-while loop in C will execute the code inside {} once,
  // and THEN evaluate the while condition.  As long as the condition is
  // true, the loop will execute the body of code again.
#define XFREE4(x)   do {free(x);x=NULL;} while (0)
  XFREE4(ptr); // works
  if (cond)
    XFREE4(ptr); // also works b/c it expands to
  if (cond)
    do {
      free(x);
      x = NULL;
    } while (0);

  // caveat: the variables you're passing to a CPP macro should be wrapped
  // in (), to prevent odd side effects.  Recall that CPP merely performs
  // string substitutions, and very little C syntax checking takes place at
  // that point.
#define XFREE5(x)   do {free(x);(x)=NULL;} while (0)

  // why: imagine a simple macro such as
#define ADD(a,b)    a+b
#define MUL(c,d)    c*d
  int i, j, k; // assume you initialized these to some values
  int x, y;
  x = ADD(i+j,k); // i+j+k
  y = MUL(i+j,k); // i+j*k: meaning i+(j*k) NOT (i+j)*k.  Bad!
  // solution, always put parentheses around each arg in a cpp macro
#define MUL2(c,d)    ((c)*(d))
}


//////////////////////////////////////////////////////////////////////
// copying, storing, and transmitting data structures
// endian-ness, and out of band data structures

struct foo1
{
  int n; // 4B (first in mem)
  unsigned short s; // 2B (next after mem space for 'n')
};

main()
{
  struct foo1 x;
  // transmit struct foo1 elsewhere, or stored it on some disk media
  init(&x); // assume the x struct is filled in with some valid values
  // assume I have a socket descriptor connected to a remote host, or I've
  // opened a file descriptor for writing.  In Unix, fd's can be for network
  // or file system objects equally.  In some OSs (e.g., Windows versions),
  // file descriptors for sockets vs. regular files cannot be mixed.
  len = write(fd, &x, sizeof(struct foo1)); // can also use sizeof(x)
  // above will write the 6-byte struct, one byte at a time, in the order in
  // which it was laid out in the memory of THIS computer, to the descriptor
  // fd.

  // NOTE: sizeof(struct foo1) may be 6 bytes or 8 bytes.  Some compilers
  // and architectures require that all structs are padded to the next size
  // of "word size".  Word size on 32-bit CPUs is 4; on 64-bit CPUs it is
  // 8.  The reason for padding is often so struct's mem can be neatly
  // aligned on a word boundary in memory (more efficient to read 4 bytes
  // when they're aligned in mem, vs. if they're split 1+3).
  // But not all systems will pad structs.

  // now suppose you're reading the same structure, on a different machine,
  // from a previously saved file, or across the network (socket).

  // PROBLEM OF ENDIANNESS: "big endian" systems store bytes as most
  // significant byte (MSB) first.  Little endian machines will
  // store in memory the least significant byte first (LSB).  So if the
  // endianness of the writer vs. reader was different, the int 'n' will be
  // corrupted (e.g., big numbers become small and vice versa).

  // SOLUTION: the Internet came together and decide to pick big-endian as
  // the standard of transmission across the network.  That means when you
  // store numbers on persistent media, you also have to use big-endian.
  // Why big-endian: most popular processor architecture (at least at the
  // time this was decided).  On the internet, big-endian form is also
  // called "network order"; and the order of endianness of the actual local
  // computer is called "host order".

  // Example: client C transmits an int to server S.
  // 1. If C is big-endian, don't need to do anything
  // 2. If C is little-endian, must convert the int to big-endian form
  // 3. If S is big-endian, don't need to do anything when receiving the int
  // 4. If S is little-endian, must convert the int to little-endian form
  //    upon receipt.
  // Meaning: worse case scenario is when a little-endian transmits to a
  // little-endian machine.

  // See man page byteorder(3): functions to convert int and short b/t host
  // and network orders.
  // See man page endian(3) for full list of network/host order macros that
  // span also 64-bit quantities.

  // Proper way to transmit struct foo1 or save it (not a single write)
  // transmit/save one element at a time, converting as needed
  int n2 = htonl(x.n); // read "native" int and convert to network order
  len = write(fd, &n2, sizeof(n2)); // can also use sizeof(int)
  unsigned short s2 = htonl(x.s); // convert native short to network order
  len = write(fd, &s2, sizeof(s2)); // can also use sizeof(unsigned short)

  // the reader of the above info will do the same, but will have to use
  // ntohl(3).  The nice thing is that all these macros, on big endian
  // machines, effectively translate to a noop.
}

//////////////////////////////////////////////////////////////////////
struct foo2
{
  int n; // 4B (first in mem)
  char *name; // null terminated string
  unsigned short s; // 2B (next after mem space for 'n')
};

main()
{
  struct foo2 x; // can also be malloc'd
  // now init the struct
  x.n = 17;
  x.s = 3;
  x.name = strdup("hello world"); // strdup malloc's a copy! need to free()
				  // later.
  // now transmit
  len = write(fd, &x, sizeof(struct foo2)); // can also use sizeof(x)
  // BUG: we know that the int and u_short have to be converted to network
  // order.
  // What will transmit for x.name?
  // Q: What is sizeof(char *)? A: all pointers' sizes are the same as the
  // arch word size (4B or 8B).
  // So we'll be transmitting the number representing the starting address
  // of the first byte of this "hello world" string.  The str itself would
  // not be transmitted, only the mem addr when the str starts in mem.

  // Suppose I wrap x.name in htonl() and send the ptr in network order?  And
  // suppose a recipient will properly convert it back using ntohl().  What
  // would a recipient of that number do with it?  The number transmitted
  // represents a virt mem addr that's ONLY valid for the sending process!
  // The receiving process would not be able to interpret that addr as a
  // valid mem addr, not in the receiving process's addr space, b/c it's a
  // different addr space!

  // Lesson: cannot transmit or "save" mem addrs b/c they're meaningless
  // beyond the sending/writing process, and only meaningful WHILE that
  // process still runs.

  // So how would I have transmitted or saved the actual string?
  // 1. transmit htonl() of x.n
  // 2. transmit htonl() of x.s
  // 3. transmit the actual bytes of x.name

  // Note, if I send the bytes in the above order, the recipient must
  // assume the same receiving order (even if it's not the same order in the
  // struct itself).

  // if you try to transmit a string over, including the null that
  // terminates it, how would a receiver/reader know how much space to
  // allocate for that string?  So you must pre-allocate some space that
  // will hold the bytes of that string, no matter how long it is.  You'll
  // wind up allocating a large buffer where perhaps you're only using a
  // small amount of it: that's a waste of mem.
  // Solution: send the size of the string first, before you send the bytes
  // of the string.  Example, if you don't expect any strings to be longer
  // than 255 bytes, you can use a single-byte to store and transmit the
  // string's length (and don't have have to worry about endianness with an
  // "unsigned char").

  // thankfully, there are libraries to perform a lot of such conversion
  // when transmitting complex structs.  See xdr(3) -- eXternal Data
  // Representation library.

  // One small problem with struct foo2: the char* is in a different mem
  // location from the rest of the struct foo2, even if you declared foo2 a
  // ptr and malloc'd it
  struct foo2 *p = malloc(sizeof(struct foo2));
  p.name = strdup(...);
  // when mem is not all close to each other, it can result in CPU cache
  // flushes, and additional mem bus operations -> causes programs to be
  // memory bound.

}

//////////////////////////////////////////////////////////////////////
// Solution: an out of band mem (OOB) structure
struct foo3
{
  int n; // 4B (first in mem)
  unsigned short s; // 2B (next after mem space for 'n')
  u_short namelen; // length of string in field 'name'
  char name[1]; // null terminated string starts here, can store variable
		// length string starting here.  Sometimes you may even see
		// a declaration such as "char name[0]".
};

main()
{
  struct foo3 *p;
  // assume I have a string that came as input and I'd want to store it
  // inside foo3 struct
  const char *input = "hello world";
  // allocate space for p to ALSO hold enough bytes of the string
  p = malloc(sizeof(struct foo3) + strlen(input));
  p->n = 17;
  p->s = 3;
  p->namelen = strlen(input);
  strcpy(p->name, input); // will copy first byte 'h' into name[0], which
			  // lives inside the foo3 struct as last byte.
			  // Rest of bytes will be copied into mem AFTER the
			  // official end of the foo3 struct.
  // can still free(p) one time, b/c it was allocated as ONE contiguous
  // unit.
  // you can printf entire string by referring to p->name.

  // Benefits: allows you to store variable length data together with
  // structures, one malloc and one free needed, all data is packed closely
  // in memory (more efficient use in memory and CPU caches).
}
