Search code examples
cstringscopeundefined-behavior

Add numbers to filename


I want to store data in different files. Therefore I want to create files as follows: data_1.log, data_2.log, ..., data_N.log. The appendix .log is not necessary but would be nice. All my approaches failed so far. Here is one sample that is probably close to what I need:

#include <stdio.h>
#include <string.h>

char get_file_name(int k){ 
    int i, j;
    char s1[100] = "logs/data_";
    char s2[100];
    snprintf(s2, 100, "%d", k);
    for(i = 0; s1[i] != '\0'; ++i);
    for(j = 0; s2[j] != '\0'; ++j, ++i){
        s1[i] = s2[j];
    }
    s1[i] = '\0';
    return s1;
}

int main(){

    char file_name[100];
    for(int k=0; k<10; k++){
        // Get data
        // ...
        // Create filename
        strcpy(file_name, get_file_name(k));
        printf("%s", file_name);
        // fp = fopen(file_name, "w+");
        // Write data to file
        // print_results_to_file();
        // fclose(fp);
    }

    return 0;
}

At the moment I get the following errors which I don't understand:

    string.c: In function ‘get_file_name’:
string.c:14:12: warning: returning ‘char *’ from a function with return type ‘char’ makes integer from pointer without a cast [-Wint-conversion]
     return s1;
            ^~
string.c:14:12: warning: function returns address of local variable [-Wreturn-local-addr]
string.c: In function ‘main’:
string.c:24:27: warning: passing argument 2 of ‘strcpy’ makes pointer from integer without a cast [-Wint-conversion]
         strcpy(file_name, get_file_name(k));
                           ^~~~~~~~~~~~~~~~
In file included from string.c:2:
/usr/include/string.h:121:14: note: expected ‘const char * restrict’ but argument is of type ‘char’
 extern char *strcpy (char *__restrict __dest, const char *__restrict __src)
              ^~~~~~

Is there a more simpler way to create such filenames? I can't believe that there isn't one.


Solution

  • There are various issues with this code and rather than correcting them one by one here’s an alternative approach. It’s not the only one but it’s simple and should be easy to understand and adapt:

    #include <stdio.h>
    
    void get_file_name(int k, char* buffer, size_t buflen) {
        snprintf(buffer, buflen, "logs/data_%d.log", k);
    }
    
    int main() {
        const size_t BUFLEN = 50;
        char file_name[BUFLEN];
    
        for (int i = 0; i < 10; i++) {
            get_file_name(i, file_name, BUFLEN);
            printf("%s\n", file_name);
            // Code for writing to file.
        }
    }
    

    A few details:

    1. Rather than attempting to return (pointers to) memory, this function passes a buffer that is written to. It’s up to the caller to ensure that the buffer is big enough (this is always the case here, but if the actual filenames are longer, you should add logic that inspects the return value of snprintf and performs appropriate error handling).

    2. The actual logic of the function requires only a single call to snprintf, which already performs everything you require, so it’s unclear whether having a separate function is even necessary or helpful.

    3. The above uses variable-length arrays. If you want to ensure constant buffers, you can use a #define instead of a const size_t variable for the buffer length. However, using a variable-length array here is fine, and some compilers even convert it into a constant array.

    As mentioned in comments, it’s important that you (a) read and understand the documentation of the functions you’re using, and (b) read and understand the compiler error messages.