我是 C 编程新手。我正在尝试将 C 结构写入文件并将其读回。我正在使用 valgrind 检查内存泄漏和潜在错误。当我运行程序时,我收到一组 valgrind 错误,但没有内存泄漏。该程序还准确地将数据写入文件并从文件中读回数据。我创建了一个单独的测试程序 ( testRead
) 来测试在运行用于写入文件的测试程序 ( testUsers
) 后读回文件,并且数据被正确读回。我还可以使用 hexdump 验证磁盘上的数据是否正确。valgrind 错误是什么意思 - 错误消息中引用的未启动字节是什么?
运行返回的错误:valgrind -s --track-origins=yes --leak-check=full --show-leak-kinds=all ./testUsers
==1155442== HEAP SUMMARY:
==1155442== in use at exit: 0 bytes in 0 blocks
==1155442== total heap usage: 11 allocs, 11 frees, 10,448 bytes allocated
==1155442==
==1155442== All heap blocks were freed -- no leaks are possible
==1155442==
==1155442== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
==1155442==
==1155442== 1 errors in context 1 of 1:
==1155442== Syscall param write(buf) points to uninitialised byte(s)
==1155442== at 0x4989887: write (write.c:26)
==1155442== by 0x48FFEEC: _IO_file_write@@GLIBC_2.2.5 (fileops.c:1180)
==1155442== by 0x49019E0: new_do_write (fileops.c:448)
==1155442== by 0x49019E0: _IO_new_do_write (fileops.c:425)
==1155442== by 0x49019E0: _IO_do_write@@GLIBC_2.2.5 (fileops.c:422)
==1155442== by 0x4900FD7: _IO_file_close_it@@GLIBC_2.2.5 (fileops.c:135)
==1155442== by 0x48F3D8E: fclose@@GLIBC_2.2.5 (iofclose.c:53)
==1155442== by 0x10991E: saveToFile (in /home/mark/ESP32-Projects/esp32-launcher/sandbox/users-binary_save/testUsers)
==1155442== by 0x109D0E: main (in /home/mark/ESP32-Projects/esp32-launcher/sandbox/users-binary_save/testUsers)
==1155442== Address 0x4aa17f6 is 6 bytes inside a block of size 4,096 alloc'd
==1155442== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1155442== by 0x48F3BA3: _IO_file_doallocate (filedoalloc.c:101)
==1155442== by 0x4902CDF: _IO_doallocbuf (genops.c:347)
==1155442== by 0x4901F5F: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:744)
==1155442== by 0x49006D4: _IO_new_file_xsputn (fileops.c:1243)
==1155442== by 0x49006D4: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1196)
==1155442== by 0x48F4FD6: fwrite (iofwrite.c:39)
==1155442== by 0x1098A5: saveToFile (in /home/mark/ESP32-Projects/esp32-launcher/sandbox/users-binary_save/testUsers)
==1155442== by 0x109D0E: main (in /home/mark/ESP32-Projects/esp32-launcher/sandbox/users-binary_save/testUsers)
==1155442== Uninitialised value was created by a heap allocation
==1155442== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1155442== by 0x1095F6: findUser (in /home/mark/ESP32-Projects/esp32-launcher/sandbox/users-binary_save/testUsers)
==1155442== by 0x1094F4: createUser (in /home/mark/ESP32-Projects/esp32-launcher/sandbox/users-binary_save/testUsers)
==1155442== by 0x1093DE: initDB (in /home/mark/ESP32-Projects/esp32-launcher/sandbox/users-binary_save/testUsers)
==1155442== by 0x109AFF: main (in /home/mark/ESP32-Projects/esp32-launcher/sandbox/users-binary_save/testUsers)
用户.h
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_USERS 3
#define MAX_USER_NAME_SIZE 20
#define PERMISSION_LAUNCH 4
#define PERMISSION_EDIT_USER 2
#define PERMISSION_EDIT_LAUNCHER 1
typedef struct {
char user_name[20];
char password[20];
int permissions;
int index;
} User;
typedef struct {
int code;
char *message;
char* payload;
} Error;
extern char* user_names[MAX_USERS][MAX_USER_NAME_SIZE];
Error create_error(int code, char *message);
Error createUser(char* user_name, char* password, int permissions);
User* findUser(char * user_name);
void printUsers();
Error initDB();
void deleteUsers();
Error saveToFile();
Error readFromFile();
用户.c
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "users.h"
/* An array of pointers to the User structs created */
static User* users_storage[MAX_USERS];
/* The number of users in the users_storage array */
int user_counter = 0;
/* File name for saving the users_storage to disk */
const char* USER_FILE_NAME = "users.dat";
/*
A struct to return error messages to the UI as needed for errors that come up.
There are no different error codes, just 0 for success and -1 for an error. The UI
Would check the error code and then display the error message.
*/
Error create_error(int code, char *message) {
Error err;
err.code = code;
err.message = message;
err.payload = "Nothing to see here.";
return err;
}
/*
Null out the users_storage and add the "admin" user to the users_storage
*/
Error initDB() {
Error result = create_error(0, "Success");
for (int i = 0; i < MAX_USERS; i++) {
if (users_storage[i] != NULL) {
free(users_storage[i]);
}
users_storage[i] = NULL;
}
int permissions = PERMISSION_LAUNCH + PERMISSION_EDIT_USER + PERMISSION_EDIT_LAUNCHER;
result = createUser("admin", "password", permissions);
return result;
}
/*
Create a new User.
- Checks for invalid permissions; it must be between 1 and 7
- Checks for duplicate users using the user name
- Creates a new user and adds it to the users_storage
- Increments the user_counter
- If the users_storage is full (ie there are MAX_USERS in the users_storage), then an error is returned
with a message for the UI that a user must be deleted before a new one can be created.
*/
Error createUser(char* user_name, char* password, int permissions) {
int user_permissions = 0;
Error result = create_error(0, "Success");
if (permissions < 0 || permissions > 7) {
result.code = -1;
result.message = "Invalid permissions. Permissions must be between 0 and 7. Permissions set to 1.";
user_permissions = 1;
}
else {
user_permissions = permissions;
}
User* duplicate = findUser(user_name);
if (duplicate != NULL) {
result.message = "Duplicate user";
result.code = -1;
return result;
}
User* user = findUser(NULL);
if (user != NULL) {
strcpy(user->user_name, user_name);
strcpy(user->password, password);
user->permissions = user_permissions;
user_counter++;
return result;
}
else {
result.message = "Out of user memory. Delete a user before creating another one.";
result.code = -1;
return result;
}
}
/*
Finds a user by user name.
- Returns NULL if the user does not exist
- Returns the User struct if the user is found
*/
User* findUser(char* user_name) {
for (int i = 0; i < MAX_USERS; i++) {
User * user = users_storage[i];
if (user == NULL && user_name == NULL) {
// found and empty user slot
User * user = malloc(sizeof(User));
user->index = i;
users_storage[i] = user;
return user;
}
if (user != NULL && user_name != NULL) {
if (strcmp(user->user_name, user_name) == 0) {
return user;
}
}
}
return NULL;
}
/*
Convenience function to print out the contents of the users_storage in a human
readable format for debugging.
*/
void printUsers() {
for (int i = 0; i < MAX_USERS; i++) {
User* user = users_storage[i];
if (user != NULL) {
printf("%i, %s, %s, %i, %i\n", i, user->user_name, user->password, user->permissions, user->index);
}
else {
printf("%i, NULL\n", i);
}
}
}
/*
Used by readFromFile to clear the users_storage.
*/
void deleteUsers() {
for (int i=0; i<MAX_USERS; i++) {
if (users_storage[i] != NULL) {
User* user = users_storage[i];
printf("deleteUsers user_name=%s\n", user->user_name);
free(user);
users_storage[i] = NULL;
}
}
}
/*
Write the users_storage to a binary file
*/
Error saveToFile() {
Error result = create_error(0, "Success");
FILE *file = fopen(USER_FILE_NAME, "wb");
if (file == NULL) {
result.message = "Error opening file";
result.code = -1;
return result;
}
size_t element_count = 0;
for (int i = 0; i < MAX_USERS; i++) {
if (users_storage[i] != NULL) {
User* user = users_storage[i];
//printf("user_name=%s, password=%s, permissions=%i, index=%i\n", user->user_name, user->password, user->permissions, user->index);
fwrite(user, sizeof(User), 1, file);
if (ferror(file)) {
perror("Error writing to file");
result.message = "Error writing to file";
result.code = -1;
fclose(file);
return result;
}
element_count++;
}
}
fclose(file);
result.payload = "User data saved in a file";
return result;
}
/*
Read the users_storage from a binary file
*/
Error readFromFile() {
Error result = create_error(0, "Success");
FILE *file = fopen(USER_FILE_NAME, "rb");
if (file == NULL) {
result.message = "Error opening file";
result.code = -1;
return result;
}
User user;
deleteUsers();
size_t read_size;
int i = 0;
int users_created = 0;
while(1) {
read_size = fread(&user, sizeof(User), 1, file);
if (read_size == 0) {
break;
}
else {
createUser(user.user_name, user.password, user.permissions);
i++;
users_created++;
}
}
fclose(file);
result.payload = "User data read from file.";
return result;
}
测试用户.c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "users.h"
int main() {
printf("InitDB: ");
Error result = initDB();
printf("%i, %s\n",result.code, result.message);
printUsers();
//create a user with bad permissions
printf("\ncreate a user ron with bad permissions (9): ");
result = createUser("ron", "password", 9);
printf("%i, %s\n",result.code, result.message);
printUsers();
// create a user
printf("\ncreate john: ");
result = createUser("john", "pw0", 4);
printf("%i, %s\n",result.code, result.message);
printUsers();
// create the same user again
printf("\ncreate john: ");
result = createUser("john", "pw0", 4);
printf("%i, %s\n",result.code, result.message);
printUsers();
// create another user when out of users
// Need to set MAX_USERS to 4 to trip this error condition
printf("\ncreate mark: ");
result = createUser("mark", "pw0", 4);
printf("%i, %s\n",result.code, result.message);
printUsers();
printf("\nSaving the file: \n");
result = saveToFile();
printf("%i, %s, %s\n",result.code, result.message, result.payload);
printUsers();
printf("\nReading the file: \n");
result = readFromFile();
printf("%i, %s, %s\n",result.code, result.message, result.payload);
printUsers();
printf("\nDeleting all the users\n");
deleteUsers();
return(0);
}
测试读取
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "users.h"
int main() {
printf("\nReading the file: \n");
Error result = readFromFile();
printf("%i, %s, %s\n",result.code, result.message, result.payload);
printUsers();
printf("\nRemove all the users from memory\n");
deleteUsers();
return(0);
}
Makefile
usermake: users.c testUsers.c
gcc -o -g -O0 -o testUsers users.c testUsers.c -I .
gcc -o -g -O0 -o testRead users.c testRead.c -I .
当您使用
malloc
为对象分配空间时,分配的字节未初始化。当您稍后使用User
写入username
和字段时,这不会写入数组的所有元素。这会使任何剩余的数组元素保持未初始化状态。password
strcpy
然后,当您将结构写入磁盘时,您正在写入整个
username
结构,包括和数组的未初始化元素password
。这就是 valgring 正在捕获的内容。您可以在此处使用
calloc
而不是malloc
来分配空间。这会将返回内存的所有字节初始化为 0,并防止读取任何未初始化的字节。findUser
与此无关,函数创建新对象是没有意义的User
。这应该是函数的责任createUser
。findUser
函数应该只查找用户。