3

I am writing an operating system in C. I have a basic file system using structs. I am writing a command line, and I want a function to create a new file. I have one set up, but it does not work. The struct initializes fine, but it is not added into the folder. Each file is a struct of the file type below:

typedef struct {
    char** content; // Address of the text in the file
    char* name;     // Filename
    size_t size;    // Included from stddef.h - set by the function
} file;

Each directory is a struct of the dir type below:

typedef struct {
    file* files[256]; // The files in the directory
    char* name;       // The directory name
    size_t index;     // The index of the next file - basically the length of the files array - starts at 0
} dir;

My new_file function is as follows:

void new(dir* folder, char* name, char* content) { 
    // Folder is a dir* so we can modify the actual struct, not a copy
    file f;
    f.name = name;
    f.content = &content;
    folder -> files[folder -> index] = &f;
    folder -> index++;
}

I am a beginner at C, but I cannot tell what the problem is. Please help!

More Info

The new function (basically):

void new(char* name, char* content, dir* folder) {
    file new = new_file(name, content);
    add_file(folder, new);
}

The add_file function:

void add_file(dir* folder, file f) {
    folder -> files[folder -> dirnum] = f;
    folder -> dirnum++;
}

The function I call to try and read the file:

char* read(dir* folder, char* name) {
    file f = find_file(*folder, name);
    return f.content;
}

With find_file being:

file find_file(folder, name) {
    for (size_t i = 0; i < folder.filenum; i++) {
        file f = *folder.files[i];
        if (strcmp(f.name, name, '\0')) {
            return f;
        }
    }
}

And strcmp being a string comparison function I wrote.
After some modifications of the source code, the following bug occurs. When you run (in the command line):

new hi file // Same as new("hi", "file", &root)
open hi // Same as read(&root, "hi")

The output is open hi.

When I run the new function, folder.files[0] exists. However, folder.files[0] -> name equals g→. Any idea why?

24
  • 8
    f.content = &content; - Remember this axiom. Anytime the address-of operator is applied to a simple id local to a specific scope (and that includes function arguments, which are by-definition local to their function), the returned address is valid only for the encompassing scope. Therefore, if whatever you're doing with the value rom &content is intended to live beyond the bounds of new() (and it certainly looks like it is), it is a mistake from inception; either there is a design flaw or a code-understanding flaw, or all-too-often, both. Commented Nov 4, 2019 at 22:00
  • 2
    If you don't have C library functions, you need to implement your own variations of them. E.g. Unix kernels have kmalloc(). Commented Nov 4, 2019 at 22:25
  • 3
    An operating system is a pretty advanced application. Why is a beginner trying to do this? Learn to walk before trying to run. Commented Nov 4, 2019 at 22:26
  • 2
    I would say that trying to do this without memory allocation is a waste of time. If you're not ready to write your own malloc, I would suggest first writing an interim version that does use the system malloc, just so you can see if the rest of your scheme is working at all well. Then you can look into weaning yourself off of the system malloc. If you're still not ready to write a general-purpose malloc, you can write alloc_file() and alloc_dir() functions that maintain static arrays of (say) 50 of these structures, and hand out pointers to them one at a time. Commented Nov 7, 2019 at 23:11
  • 1
    You might also want to make the name -- and perhaps even the content -- fields fixed-size arrays for now, rather than pointers. That way you can defer the problem of allocating space for them, too. Commented Nov 7, 2019 at 23:13

2 Answers 2

2
+50

issue is

void new(dir* folder, char* name, char* content) { 
// Folder is a dir* so we can modify the actual struct, not a copy
   file f;
   .......
   folder -> files[folder -> index] = &f;

here f is in local variable.

storing address of which is not wise thing to do.

alternatively you can do one of these two things

  1. easiest is declare global array of files and use them. // only to test its very bad idea but easy enough to check that this solves issue.
  2. proper thing is allocate memory using likes of malloc and free it when deleted.

in actual file system this list is mentained on disk. refer easiest file system to study i.e. FAT16

I have used chans FATFS implementation. it is easy to understand. and well documented.

Sign up to request clarification or add additional context in comments.

3 Comments

The problem persists even after making f global and changing &f to f, making folder -> files a file[], not file*[].
Can you try just making f global ? (it will wouk only for one file) don't change &f to f is wrong.
Suggestion if you are compiling with gcc use -Wall -Werror. to know all possible issues. I used in my learning days.
0

I looked like you wanted to know the problem of your code. Thus, I updated this answer with the reason below the original answer.

Without much information and the ability to use malloc, I can only offer you this very simple memory management code. Note that this is just an example, you will have to learn memory management on your own and write your own malloc if you have to work in an environment without malloc.

Note that you can also use stack memory to create all your variables. Although stack memories are fast, there are limitations to stack memories.

For the code below, it is an example code. Thus, I only used the simplest memory management method. The code below uses mmap() to request memory from the system.

For the code's memory management method, it requests a block of memory from the system to be used as a pool of free memory. Each time any method call requestMem, it will try to get a block of unused memory from the memory pool that is requested from the system. If the requestMem function could obtain a block of free memory from its pool then it will return a pointer to the beginning of that free block of memory. The pointer type that is returned will always be of type void. Unless it couldn't get any memory. In that case, NULL will be returned.

Most of the comments are written in the code. Also, I changed your **content to just *content. Besides that, I replaced your strcmp() with the normal library's strcmp(). I also rename your read() to read2().

Note that, I did test the method but not thoroughly as it is an example. It just meant to help you understand a bit about memory management. Note that, I did not write a method for reallocating memory like realloc() nor did I write a method to release the memory of each pointer. Nonetheless, I did design it in a way that you can invent your own free() for each pointer. You can also design your own memory defragmentation after each free by copying(moving) the later blocks into the spaces of the just release block.

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

#define __PMAX__ 500

typedef struct __membin__ __membin__;
int freebin(__membin__ * bin);
void * _requestMem( size_t size );
void * __requestMem( size_t size );
void * ___requestMem( size_t size );

/* Memory Bin is membin */
__membin__ membin;
/*
 * Use requestMem to request memory from membin.
 * Read the comments from main() to understand more 
 * about requestMem.
 * 
 **/
void * (*requestMem)(size_t) = &_requestMem;

/* 
 * Struct for memory bin
 */
typedef struct __membin__ {
    void * mempage;
    intptr_t from;               /* <- memory page from: inclusive */
    intptr_t to;                 /* <- memory page to: inclusive */

    intptr_t pointer[__PMAX__];  /* <- Pointers */
    intptr_t pTo[__PMAX__];      /* <- Address to for pointer: inclusive */    
    size_t pUsed;                /* <- Pointer used */

    size_t size;                 /* <- Actual size */
    size_t sUsed;                /* <- Size Used */
} __membin__;

/* Free the memory bin */
int freebin(__membin__ * bin){
    int code = munmap(bin->mempage, bin->size);
    if ( code == 0 ){
        bin->size = 0;
    }
    return code;
}

/* 
 * This function initialize the memory bin.
 * This function use mmap to allocate a large memory block with the 
 * size of sysconf(_SC_PAGESIZE) * 20. That memory block is converted
 * to a void pointer then assigned to membin->mempage.
 * 
 * Initial size of membin can be configured to be larger.
 **/
int membinInit(__membin__ * bin){
    size_t initSize = sysconf(_SC_PAGESIZE) * 20;
    bin->mempage = (void *)mmap(NULL, initSize, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE,-1,0);

    if (bin->mempage == MAP_FAILED) return 1;  

    bin->from = (intptr_t) bin->mempage;
    bin->to = bin->from + (intptr_t) initSize - 1;

    bin->pUsed = 0;
    bin->size = initSize;
    bin->sUsed = 0;

    return 0;
}

/*
 * On first run, requestMem is assigned to this function _requestMem.
 * After this function initialize the memory bin successfully it will 
 * assign requestMem to be a pointer of function __requestMem. 
 * 
 * On unsuccessful case of membin initialization, requestMem will be a 
 * pointer to the ___requestMem function that always return null.
 *
 */ 
void * _requestMem( size_t size ){
    int code = membinInit(&membin);
    if ( code == 1 ) {
        requestMem = &___requestMem;
        return NULL;
    } 

    requestMem = &__requestMem;
    return __requestMem(size);
}

/*
 * __requestMem is a function that will get a block of memory from the
 * unused portion of memory of membin->mempage. 
 * 
 * If the requested size is larger than what available in membin then 
 * NULL will be returned. 
 * 
 * If the requested size is 0 then a value NULL will also be returned.
 */
void * __requestMem( size_t size ){
    size_t new_sUsed = size + membin.sUsed;
    if ( size == 0 
         || new_sUsed > membin.size 
         || membin.pUsed >= __PMAX__ ) return NULL;

    intptr_t p = membin.from + membin.sUsed;

    membin.pointer[membin.pUsed] = p;
    membin.pTo[membin.pUsed] = p + size - 1;
    membin.pUsed++;

    membin.sUsed = new_sUsed;

    return (void * ) p;
}

/*
 * This function will always return NULL.
 * This function name can be changed to NULL returned function.
 */
void * ___requestMem( size_t size ){
    return NULL;
}

typedef struct {
    char * content; // Address of the text in the file
    char * name;    // Filename
    size_t size;    // Included from stddef.h - set by the function
} file;

typedef struct {
    file * files[256]; // The files in the directory
    char * name;       // The directory name
    size_t index;      // The index of the next file - basically the length of the files array - starts at 0
} dir;

void new_file(dir* folder, char* name, char* content);
void add_file(dir* folder, file * f);
char* read2(dir* folder, char* name);
file * find_file(dir * folder, char * name);

void new_file(dir* folder, char * name, char * content){ 
    // Folder is a dir * so we can modify the actual struct, not a copy   
    /*
     * Using requestMem to request a block of memory from membin for f.
     *
     **/
    file * f = (file * ) requestMem(sizeof(file));
    f->name = name;
    f->content = content;
    folder->files[folder->index] = f;
    folder->index++;
}

void add_file(dir* folder, file * f) {
    folder->files[folder->index] = f;
    folder->index++;
}

char * read2(dir * folder, char* name) {
    file * f = find_file(folder, name);
    if ( f != NULL ) return f->content;
    else return NULL;
}

file * find_file(dir * folder, char * name) {
    for (size_t i = 0; i < folder->index; i++) {
        if (strcmp(folder->files[i]->name, name) == 0 ) {
            return folder->files[i];
        }
    }

    return NULL;
}

int main(void) {

    /* Testing membin */
    char * s = requestMem( sizeof(char) * 15 );
    char * s2 = requestMem( sizeof(char) * 24 );

    /* Check for membin size */
    printf("membin size : %zu bytes \n\n", membin.size);
    /* checking if the pointer address of s is equal to the pointer of
     * membin.pointer[0]. They should be equal.
     * 
     * membin.pTo[0] should be the last memory address for s. Of which should
     * be s' address + s' size - 1.
     */
    printf("pointer s: %lld, membin.pointer[0]: %lld, membin.pTo[0]: %lld \n\n", (long long)s,(long long) membin.pointer[0], (long long)membin.pTo[0]);
    /* checking if the pointer address of s2 is equal to the pointer of
     * membin.pointer[1]. They should be equal. Also s2 pointer 
     * should be the value of membin.pTo[0] + 1.
     * 
     * membin.pTo[1] should be the last memory address for s2. Of which should
     * be s2's address + s2's size - 1.
     */
    printf("pointer s2: %lld, membin.pointer[1]: %lld, membin.pTo[1]: %lld \n\n", (long long)s2, (long long)membin.pointer[1], (long long)membin.pTo[1]);
    /* 
     * If requestMem worked correctly, when membin is not yet initialized
     * requestMem will have same address of _requestMem.
     * 
     * If after first run sucessful and membin is initialized requestMem
     * will have the same address as __requestMem.
     * 
     * If initialize of membin fail, requestMem will have the
     * same address as ___requestMem.
     */
    printf("requestMem:    %p\
          \n_requestMem:   %p\
          \n__requestMem:  %p\
          \n___requestMem: %p\
          \n\n", requestMem, _requestMem, __requestMem, ___requestMem);

    /* File Testing */
    dir root;
    root.name = "root";
    root.index = 0;

    new_file(&root, "Hi", "I am the content.");

    file * foundFile = find_file(&root,"Hi");
    if ( foundFile != NULL ){
      printf("Found the file:\n");
      printf("Name: %s\n", foundFile->name);
      printf("Content: %s\n\n", foundFile->content);
    }
    /* End File Testing*/


    /* Add all the sizes together to see if the size used in membin is right.*/
    printf("Size of struct file: %zu bytes\n", sizeof(file));    
    printf("membin sUsed: %zu bytes\n", membin.sUsed);

    /* 
     * Only free bin when everything is finished, because this code is an example, 
     * it did not contain a reinitialize code.
     */
    freebin( &membin );
    /* membin size should be a value of after using freebin */
    printf("membin size after freed: %zu", membin.size);

    return 0;
}

Your case is interesting and the type of error your code produced is not the same across compilers. The problem with your struct assignment is the address to the local variable like how I commented below your question. That can be fixed with global variables. However, what can't be fixed and does not produce the same error across compiler is the assignment of an address of a pointer to chars to content. That one can't be fix with a global variable. Even when all data became static and have their own address, some compiler still showed error and some doesn't. I produced the example below which can produce the exact error you have but is simpler.

Go run the code below on https://repl.it/languages/c. Run it more than 10x. Sometimes, the first test.c will become null. On the other hand, when I run it on my gcc version 7.4.0 compiler for linux on Ubuntu, test.c never display the correct result the second time.

Rule of thumbs? Don't assign the pointer to an address of a parameter of a function? Or is it because that "pointer to pointer to chars" had a local address?

#include <stdio.h>

typedef struct {
  char ** c;

} __test__;

__test__ test;

void fb (char * input );

void fa (char * input, int opt){
    if ( opt == 1 ) return;
    fb(input);
};

void fb ( char * input ){
    test.c = &input;
};

int main(void) {
    /* Test running this more than 10x and tell me what you think*/
    fa("Hello", 0);
    printf("%s\n", *test.c);
    fa("This should not change c", 1);
    fa("This should also not change c but for some reason sometimes c become null", 1);
    printf("%s\n", *test.c);

    printf("\n\n");

    char * str = "Hello2"; 
    fa(str, 0);
    printf("%s\n", *test.c);
    fa("This should not change c", 1);
    fa("On some system c will show weird result on some this work perfectly fine. I guess the pointer\
        to the address of str get reset sometimes. Or is it the pointer to the parameter of fb getting reset? Or is it because the 'pointer to pointer' is a local address?", 1);
    printf("%s", *test.c);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.