b.g.
b.g.

Reputation: 850

C memory mapped struct array leak

I have two files, which I'm trying to IPC by shared memory. I allocate using similar statements in both files. In the server file:

int fd;
int size = MAX_LEN;
int bigSize = sizeof(struct region)+ size * sizeof(struct client_message) + size * sizeof(struct server_message);
    struct region *rptr = (struct region*)malloc(bigSize);
    printf("region size: %d clientMessage size: %d serverMessage size: %d and the total size: %d\n", sizeof(struct region), size * sizeof(struct client_message), size * sizeof(struct server_message), bigSize);
    fd = shm_open("/myregion", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);

    if (ftruncate(fd, bigSize)  == -1)
        printf("error creating ftruncate\n");

    rptr = mmap(NULL, sizeof(struct region),
                PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

I've used the example on the following link to dynamically allocate shared memory: C Windows - Memory Mapped File - dynamic array within a shared struct

struct client_message {
    pthread_t client_id;
    int question;
};
struct server_message {
    pthread_t client_id;
    pid_t server_id;
    int answer;
};
struct region {        /* Defines "structure" of shared memory */
    int len;
    struct client_message ptr_client_message[0];
    struct server_message ptr_server_message[0];
};

when I assign in a while loop and increasing j in this server file,

(rptr->ptr_client_message[(j)%size]).question = 30;
(rptr->ptr_server_message[(j)%size]).answer = 20;

I read it from the client file as such:

printf("rptr len is %d and question of client %d is: %d, answer of server is %d \n", size, k%size, (rptr->ptr_client_message[(k)%size]).question, (rptr->ptr_server_message[(k)%size]).answer);

The outputs are mindboggling: from the server terminal I get:

rptr len is 10 and question of client 0 is: 30, answer of server is 20 
rptr len is 10 and question of client 1 is: 30, answer of server is 20 
rptr len is 10 and question of client 2 is: 30, answer of server is 20 
...

changing for 10 elements of the client_message array, i.e. up to client [MAX_LEN]

from the client terminal I get:

rtpr len is 10 and question of client 0 is: 30, answer of server is 20
rptr len is 10 and question of client 1 is: 30, answer of server is 30
rptr len is 10 and question of client 2 is: 30, answer of server is 20
rptr len is 10 and question of client 3 is: 30, answer of server is 30
rptr len is 10 and question of client 4 is: 30, answer of server is 20
rptr len is 10 and question of client 5 is: 30, answer of server is 30
rptr len is 10 and question of client 6 is: 30, answer of server is 20
rptr len is 10 and question of client 7 is: 30, answer of server is 20
rptr len is 10 and question of client 8 is: 30, answer of server is 20
rptr len is 10 and question of client 9 is: 30, answer of server is 20
rptr len is 10 and question of client 0 is: 30, answer of server is 20
rptr len is 10 and question of client 1 is: 30, answer of server is 30
rptr len is 10 and question of client 2 is: 30, answer of server is 20
rptr len is 10 and question of client 3 is: 30, answer of server is 30
rptr len is 10 and question of client 4 is: 30, answer of server is 20
rptr len is 10 and question of client 5 is: 30, answer of server is 30
rptr len is 10 and question of client 6 is: 30, answer of server is 20
rptr len is 10 and question of client 7 is: 30, answer of server is 20
rptr len is 10 and question of client 8 is: 30, answer of server is 20

So, the entries in the struct region are mixed when reached from another process. How can I prevent this?

Upvotes: 0

Views: 193

Answers (1)

Schwern
Schwern

Reputation: 164919

The problem is you can't use the zero length array trick twice in one struct.

The trick you're using in struct region is a GCC extension to allow you to have variable length arrays in a struct. The zero length array acts as a header, and then you can just remember that you tacked as much memory on the end as you like.

// This starts at rptr->client_message and advances two indexes.
rptr->client_message[2] = ...;

The problem is there's another zero length array immediately after rptr->client_message, rptr->server_message. So when you write to rptr->client_message you're overwriting rptr->server_message.

Let's simplify things and you'll see why.

struct region {
    int len;
    char ptr_client_message[0];
    char ptr_server_message[0];
};

Initialize it in the same way you do.

size_t size = 4;
struct region *rptr = malloc(sizeof(struct region) + size + size);

Now we seemingly have place for two 3 character strings. Let's add one.

rptr->ptr_server_message[0] = 'a';
rptr->ptr_server_message[1] = 'b';
rptr->ptr_server_message[2] = 'c';
rptr->ptr_server_message[3] = '\0';

printf("server_message: %s\n", rptr->ptr_server_message);

That's fine, it prints abc. Now let's populate rptr->ptr_server_message.

rptr->ptr_client_message[0] = '1';
rptr->ptr_client_message[1] = '2';
rptr->ptr_client_message[2] = '3';
rptr->ptr_client_message[3] = '\0';

printf("client_message: %s\n", rptr->ptr_client_message);

That's fine, it prints 123. What about rptr->ptr_server_message?

printf("server_message: %s\n", rptr->ptr_server_message);

That prints 123 as well! It turns out they point at the same memory.

// 0x7ff5f1404144 0x7ff5f1404144
printf("%p, %p\n", rptr->ptr_client_message, rptr->ptr_server_message);

So, you can't have two zero length arrays in one struct.

Upvotes: 1

Related Questions