Learn C Tutorial: SD Minimal.side - Making file available to multiple functions

The sample SD Minimal.side here opens a file to write then opens same file again to read.
At time of write it creates the fp object and opens. For read it re-opens the existing fp object. Write and Read operations are both in one function main().
A function other than main() does not have access to fp. What if the write and read are in 2 functions other than main()?
How to write and place the creation of fp so it is available to any function?
- thanks
/*
  SD Minimal.side
  Create test.txt, write characters in, read back out, display.
*/

#include "simpletools.h"                      // Include simpletools header    

int DO = 22, CLK = 23, DI = 24, CS = 25;      // SD card pins on Propeller BOE

int main(void)                                // main function
{
  sd_mount(DO, CLK, DI, CS);                  // Mount SD card
  FILE* fp = fopen("test.txt", "w");          // Open a file for writing
  fwrite("Testing 123...\n", 1, 15, fp);      // Add contents to the file
  fclose(fp);                                 // Close the file
 
  char s[15];                                 // Buffer for characters
  fp = fopen("test.txt", "r");                // Reopen file for reading
  fread(s, 1, 15, fp);                        // Read 15 characters
  fclose(fp);                                 // Close the file

  print("First 15 chars in test.txt:\n");     // Display heading
  print("%s", s);                             // Display characters
  print("\n");                                // With a newline at the end
}

Comments

  • Your exact verbage "available to any function" implies you need a global variable, but that is generally (strongly) discouraged in clean code.

    However, if you really want to use globals...
    #include "simpletools.h"                      // Include simpletools header    
    
    int DO = 22, CLK = 23, DI = 24, CS = 25;      // SD card pins on Propeller BOE
    
    FILE *fp;
    
    void doWrite () {
      fp = fopen("test.txt", "w");          // Open a file for writing
      fwrite("Testing 123...\n", 1, 15, fp);      // Add contents to the file
      fclose(fp);                                 // Close the file
    }
    
    void doRead(char *s, unsigned int length) {
      fp = fopen("test.txt", "r");                // Reopen file for reading
      fread(s, 1, 15, fp);                        // Read 15 characters
      fclose(fp);                                 // Close the file
    }
    
    int main(void)                                // main function
    {
      sd_mount(DO, CLK, DI, CS);                  // Mount SD card
      doWrite();
    
      char s[15];                                 // Buffer for characters
      unsigned int length = sizeof(s);
      doRead(s, length);
    
      print("First 15 chars in test.txt:\n");     // Display heading
      print("%s", s);                             // Display characters
      print("\n");                                // With a newline at the end
    }
    

    Now, in this case, "fp" doesn't need to be shared. We could make fp local to both doWrite and doRead with no ill effect.
    #include "simpletools.h"                      // Include simpletools header    
    
    int DO = 22, CLK = 23, DI = 24, CS = 25;      // SD card pins on Propeller BOE
    
    void doWrite () {
      FILE *fp = fopen("test.txt", "w");          // Open a file for writing
      fwrite("Testing 123...\n", 1, 15, fp);      // Add contents to the file
      fclose(fp);                                 // Close the file
    }
    
    void doRead(char *s, unsigned int length) {
      FILE *fp = fopen("test.txt", "r");                // Reopen file for reading
      fread(s, 1, 15, fp);                        // Read 15 characters
      fclose(fp);                                 // Close the file
    }
    
    int main(void)                                // main function
    {
      sd_mount(DO, CLK, DI, CS);                  // Mount SD card
      doWrite();
    
      char s[15];                                 // Buffer for characters
      unsigned int length = sizeof(s);
      doRead(s, length);
    
      print("First 15 chars in test.txt:\n");     // Display heading
      print("%s", s);                             // Display characters
      print("\n");                                // With a newline at the end
    }
    

    All that is being shared is the space for an address, because fp is a pointer. No significant change in the above two samples. However, to show how global variables work, I'll show two other examples too:
    int main () {
      int x = 2 + 4;
      printf("x = %d\n", x);
    
      x = x + 3;
      printf("x = %d\n", x);
    
      return 0;
    }
    

    We expect the above to print 6 and then 9. This is because the second assignment references itself in the assignment. Compare that with the second assignment of fp in your example "fp = fopen("test.txt", "r")". Notice that fp is not referenced in the assignment - the old value is completely ignored and overwritten.

    We could break my example with x into separate functions with a global variable though:
    int x;
    
    void doFirst() {
      x = 2 + 4;
    }
    
    void doSecond () {
      x = x + 3;
    }
    
    int main () {
      doFirst();
      printf("x = %d\n", x);
    
      doSecond();
      printf("x = %d\n", x);
    
      return 0;
    }
    

    And this will print the same as above. However, clean code suggests removing all global variables (constants are okay, just not variables that changes)
    int doFirst() {
      return 2 + 4;
    }
    
    int doSecond (int localX) {
      return localX + 3;
    }
    
    int main () {
      int x = doFirst();
      printf("x = %d\n", x);
    
      x = doSecond(x);
      printf("x = %d\n", x);
    
      return 0;
    }
    

    And this will again print 6 and then 9, but without the need for globals.
    David
    PropWare: C++ HAL (Hardware Abstraction Layer) for PropGCC; Robust build system using CMake; Integrated Simple Library, libpropeller, and libPropelleruino (Arduino port); Instructions for Eclipse and JetBrain's CLion; Example projects; Doxygen documentation
    CI Server: https://ci.zemon.name?guest=1
  • JasonDorieJasonDorie Posts: 1,930
    edited 2016-04-12 - 21:45:37
    A function other than main() does not have access to fp. What if the write and read are in 2 functions other than main()? How to write and place the creation of fp so it is available to any function?
    int main(void)                                // main function
    {
      sd_mount(DO, CLK, DI, CS);                  // Mount SD card
      FILE* fp = fopen("test.txt", "w");          // Open a file for writing
      fwrite("Testing 123...\n", 1, 15, fp);      // Add contents to the file
      fclose(fp);                                 // Close the file
     
      char s[15];                                 // Buffer for characters
      fp = fopen("test.txt", "r");                // Reopen file for reading
      fread(s, 1, 15, fp);                        // Read 15 characters
      fclose(fp);                                 // Close the file
    
      print("First 15 chars in test.txt:\n");     // Display heading
      print("%s", s);                             // Display characters
      print("\n");                                // With a newline at the end
    }
    

    "Available to any function" is probably not what you want. Available to specific functions is pretty easy though. It's also worth noting that in this case, the variable "fp" is a pointer to a completely different thing when used for reading and writing. Have a look:
      FILE* fp = fopen("test.txt", "w");          // Open a file for writing
      fwrite("Testing 123...\n", 1, 15, fp);      // Add contents to the file
    

    This bit of code declares a variable called fp (on the stack) that is a pointer to a FILE object. That pointer is returned by the fopen() function, in this case opening a file for write-only access (the "w" at the end of fopen). The file is written to, then closed. That means that the fp variable is now pointing at memory it doesn't own any more - that object has been thrown away during the fclose() call.

    The next bit of code:
      fp = fopen("test.txt", "r");                // Reopen file for reading
      fread(s, 1, 15, fp);                        // Read 15 characters
      fclose(fp);                                 // Close the file
    

    ...opens a file, but this time for reading. It doesn't matter that it's the same file that you wrote previously because it's being opened in a completely different mode ("r", meaning read-only) so the fp variable is pointing to a different object with new contents, it's just re-using the fp variable to point to it because it's convenient.

    Now, if you want to be able to read from or write to this file in a number of different functions, that's a different question, with a pretty easy answer. Pass the fp variable around, like this:
    void WriteSomeOtherStuff( FILE * fileHandle )
    {
      const char * localString = "This is the WriteSomeOtherStuff function\n";
      int length = strlen( localString );
    
      fwrite( localString, 1, length, fileHandle );
    }
    
    
    int main(void)
    {
      FILE* fp = fopen("test.txt", "w");          // Open a file for writing
      fwrite("Testing 123...\n", 1, 15, fp);      // Add contents to the file
      WriteSomeOtherStuff( fp );
      fclose(fp);                                 // Close the file
    }
    

    You can pass the pointer variable fp around just like anything else. Notice that I gave it a different name in my "WriteSomeOtherStuff" function - the 'fp' variable is being passed as an argument to the function, and has the local alias 'fileHandle', but it's the same object in use in both places.
  • David & Jason: You are both very generous with your time and skills. I'll sit down and go through this code to understand the implications.

    So far I think I get:

    I thought it would be more efficient to create the fp once. But that is not right because it is just a pointer that will be pointing to "nothing" after each use. The only preservation would be the name, not the pointer value and not the file to which the pointer referred.

    In general, rather than create global variables, pass the values as parameters when needed.

    Much thanks.
  • If you were to open a file for reading (or writing) and pass the handle around to a variety of functions to pull data out of the file that would be more efficient than opening & closing it multiple times, but yes, your description above sounds like you got the important bits.
  • Much thanks. I'm re-writing my code now.
Sign In or Register to comment.