Extending a 2D array using realloc is no small feat.
Because realloc can move the memory, you need to make sure you capture the possibly changed pointer.

If you're dealing with only one at once, you can use normal function pass by value and return.
But if you're trying to extend several parallel arrays all at once, it's messy!

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


char **extender1( char **fname, int *count ) {
  char **tmp = realloc(fname, sizeof(*fname)*((*count)+1));
  if ( tmp ) {
    count += 1;
    return tmp;
  } else {
    return fname;
  }
}

// https://wiki.c2.com/?ThreeStarProgrammer
void extender2( char ***fname, int *count ) {
  char **tmp = realloc(*fname, sizeof(**fname)*((*count)+1));
  if ( tmp ) {
    count += 1;
    *fname = tmp;
  }

  // if you wanted to actually use the 2D array, it would be
  // (*fname)[i]
}

int main()
{
  int count = 10;

  //read in all the data for the students
  char **fname = malloc(count * sizeof *fname);
  for (int i = 0; i < count; i++) {
    fname[i] = malloc(20 * sizeof *fname[i]);
  }

  // extend inline
  char **tmp = realloc(fname, sizeof(*fname)*(count+1));
  if ( tmp ) {
    fname = tmp;
    count += 1;
  }

  // extend via a function, one way
  fname = extender1( fname, &count );

  // extend via a function, another way
  extender2( &fname, &count );
}