Search code examples
arrayscfunctionpointersglobal

File pointer declaration interferes with a local variable in an unrelated function


So for an assignment I have to write a program that more or less plays scrabble, the basic structure is a function reads an input word, and counts how many times each letter appears in it into an array of ints with size 26. This allows the computer to compare that to the number of letters in the player hand, and normally, it works well:

//Define
void getLetterDistribution(char *word, int *dist){
 for(int i=0;i<26;i++){
  printf("%c",i+65);
 }
 printf("\n");
 for(int i=0;i<26;i++){
  int j = 0;
  do{
   if(word[j] == i+65){ //The capital letters begin at 65 in the ascii table
    dist[i]++; //Increase the count for that letter
   }
   j++;
  }while(word[j] != '\0'); //Wait for end of word
  printf("%d",dist[i]);
 }
 printf("%ls\n",dist);
}

//Call
void gameLoop(){
 char testHand[12] = "ACEROLAORIO\0"; //TEST INVOCATIONS
 int testHandDist[26];
 do{
  displayWorld();
  printf("Hand: %s\n",testHand);
  acceptInput();
  getLetterDistribution(testHand,handDist);
  getLetterDistribution(buffer,guessDist);
  printf("%d\n",compareCounts(guessDist,handDist));//TEST INVOCATIONS
 }while(isDone()!=1);
}

You can see here, the code for debugging purposes is printing out each element of the array in memory pointed to by the local variable dist after it finishes counting all the appearances of that letter into that array, underneath a line of the letters for reference. Probably not efficient but I'm a student. To meet the design specs of the assignment this function is then called in gameLoop() which will be called in the main() after init() and run exactly once (isDone() currently always returns 1, but will later be modified to be a win-loss checker)

--------------------------
Hand: ACEROLAORIO
Enter a Guess: core
CORE
ABCDEFGHIJKLMNOPQRSTUVWXYZ
20101000100100300200000000
ABCDEFGHIJKLMNOPQRSTUVWXYZ
00101000000000100100000000
1

You can see this console output I copied shows the player hand "ACEROLAORIO" letter distribution, followed by the letter distribution of the word "CORE". It also puts a "1" or "0" if the word you entered can be formed with your hand, using a function called compareCounts().

But now, it has to check the word against a dictionary to make sure it's valid, so literally all I did was add a declaration a file pointer to open the dictionary.txt to the init() function.

Before

int init(){
 srand(time(NULL));
 return 0;
}

After

int init(){
 srand(time(NULL));
 FILE* dictionary = fopen("dictionary.txt","r");
 return 0; //Placeholder
}

New Output

--------------------------
Hand: ACEROLAORIO
Enter a Guess: core
CORE
ABCDEFGHIJKLMNOPQRSTUVWXYZ
-19329427032558-195148716325586984704112195269939371221952698470411219526993937122195369847041021952-195303658325580200405811584327644058118803276469846781321952
ABCDEFGHIJKLMNOPQRSTUVWXYZ
00101000000000100100000000
0

And now, while the submitted word is still producing a valid letter distribution, the player hand is producing nonsense and gibberish, which the comparator can't read.

The only difference between the two is that the submitted word's distribution array is declared as a global variable, whereas the player hand's distribution array is currently declared as a local variable in the gameLoop() function for testing purposes because I haven't written a function to randomize the player's hand yet.

I could patch this by declaring a the player hand's distribution array a global variable, but I want to know why this is happening, why the simple act of declaring a file pointer in another function is messing with this local variable, so I can avoid making this mistake again. Furthermore I suspect it's a problem with how I've designed the function's use of pointers, and proper pointer use is something the prof wants to make sure we learn.

The complete and unabridged source code is here: https://pastebin.com/hgJ8VhWs


Solution

  • getLetterDistribution seems to assume that the dist array it's passed is initialized to all zeros. That's true for global objects by default, but not for locals unless they are explicitly initialized.

    The contents of an uninitialized local object are "garbage", and often this garbage is dependent on how stack memory is used by other parts of the program. So if you're reading uninitialized data, it's not surprising that unrelated changes to the program would change its behavior.

    You can initialize testHandDist with int testHandDist[26] = { 0 };. (If you explicitly initialize any elements of an array, the rest are all automatically initialized to 0.)

    However, instead you probably want to change getLetterDistribution to set every element of the dist array to 0 before starting; after all, getLetterDistribution knows what it needs, and the programmer shouldn't be expected to remember that every time they call it. Note that as it stands, even with a global handDist you would still have a bug if your loop iterated more than once, because on the second iteration handDist wouldn't be all zeros anymore.