c - 库存计划,可能的边界值问题?

标签 c struct inventory boundary off-by-one

下面的程序在大多数情况下似乎运行良好,但如果我添加最后一个库存位置的记录(记录 10),则会导致问题。具体来说,如果我添加记录 10 然后尝试删除它,在我调用 printList( ) 后它仍然显示在 list 中。这仅适用于最终记录,其他任何记录都不会发生这种情况。

谁能找出问题所在吗?我在使用 gcc 编译时一直使用 -Wall ,并且它没有发出任何警告。我也一直在尝试弄清楚如何使用 gdb,但我仍在学习中,所以这也没有帮助。

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

typedef struct {
  unsigned int record;
  char tool[30];
  unsigned int quantity;
  double price;
} hardware;

void menu(FILE *fPtr, hardware *toolsPtr);
void initialiseRecords(FILE * fPtr, hardware *toolsPtr);
void inputTool(FILE * fPtr, hardware *toolsPtr);
void printList(FILE * fPtr, hardware *toolsPtr);
void deleteRecord(FILE * fPtr, hardware *toolsPtr);

int main(void)
{
  
  FILE *fPtr;
  hardware tools = { 0, "", 0, 0.0 }, *toolsPtr;
  toolsPtr = &tools;

  if ((fPtr = fopen("hardware.dat", "wb+")) == NULL) {
    puts("File cannot be opened.") ;
  }
  else {
    menu(fPtr, toolsPtr);
  }

  fclose(fPtr);
}

void menu(FILE *fPtr, hardware *toolsPtr)
{
  unsigned int choice;
  
  printf("\n%s\n\n%s\n\n%s\n%s\n%s\n%s\n%s\n\n%s",
         "** Hardware Inventory Program **", 
         "    Enter a menu option:",
         "1 - Initialise the record file.",
         "2 - Add a record to the file.",
         "3 - Delete a record from the file.",
         "4 - Print the current inventory",
         "5 - Quit the program.", "> ");

  scanf("%u", &choice);

  while (choice != 5) {

    switch (choice) {
    case 1:
      initialiseRecords(fPtr, toolsPtr);
      break;
    case 2:
      inputTool(fPtr, toolsPtr);
      break;
    case 3:
      deleteRecord(fPtr, toolsPtr);
      break;
    case 4:
      printList(fPtr, toolsPtr);
      break;
    default:
      puts("Incorrect entry.");
      break;
    }

    printf("\n%s\n\n%s\n\n%s\n%s\n%s\n%s\n%s\n\n%s",
           "** Hardware Inventory Program **", 
           "    Enter a menu option:",
           "1 - Initialise the record file.",
           "2 - Add a record to the file.",
           "3 - Delete a record from the file.",
           "4 - Print the current inventory",
           "5 - Quit the program.", "> ");

    scanf("%u", &choice);
  }
}

void initialiseRecords(FILE * fPtr, hardware *toolsPtr)
{
  fseek(fPtr, 0, SEEK_SET);

  for (unsigned int i = 0; i < 10; ++i) {

    char s[30] = "";
    sscanf(s, "%s", toolsPtr->tool);

    toolsPtr->record = i + 1;
    toolsPtr->quantity = 0;
    toolsPtr->price = 0.0;

    fwrite(toolsPtr, sizeof(hardware), 1, fPtr);
  }
}

void inputTool(FILE * fPtr, hardware *toolsPtr)
{
  printf("\n%s\n\n%s", "Enter record # (-1 to quit):", "> ");
  scanf("%u", &toolsPtr->record);

  while(toolsPtr->record != -1) {

    fseek(fPtr, (toolsPtr->record - 1) * sizeof(hardware), SEEK_SET);
    fread(toolsPtr, sizeof(hardware), 1, fPtr);
    getchar();

    if (!strcmp(toolsPtr->tool, "")) {

      printf("\n%s\n\n%s", "Enter tool name:", "> ");
      fgets(toolsPtr->tool, 30, stdin);

      toolsPtr->tool[strlen(toolsPtr->tool) - 1] = '\0';

      printf("\n%s\n\n%s", "Enter quantity:", "> ");
      scanf("%u", &toolsPtr->quantity);

      printf("\n%s\n\n%s", "Enter price:", "> ");
      scanf("%lf", &toolsPtr->price);

      fseek(fPtr, (toolsPtr->record - 1) * sizeof(hardware), SEEK_SET);
      fwrite(toolsPtr, sizeof(hardware), 1, fPtr);
    }
    else {
      getchar();
      puts("There is an existing record with this number.");
      fseek(fPtr, 0, SEEK_SET);
    }
    printf("\n%s\n\n%s", "Enter record (-1 to quit):", "> ");
    scanf("%u", &toolsPtr->record);
  }
}

void printList(FILE * fPtr, hardware *toolsPtr)
{
  fseek(fPtr, 0, SEEK_SET);
  printf("\n%-10s%-30s%-10s%-10s\n\n", "Record #", 
             "Tool Name", "Quantity", "Price");

  for (unsigned int i = 0; i < 10; ++i) {

    fread(toolsPtr, sizeof(hardware), 1, fPtr);
    printf("%-10u%-30s%-10u$%-10.2lf\n",
           toolsPtr->record, toolsPtr->tool, 
           toolsPtr->quantity, toolsPtr->price);
  }
}

void deleteRecord(FILE * fPtr, hardware *toolsPtr)
{
  printf("\n%s\n\n%s", 
             "Enter the record number of the tool to delete:", "> ");
  scanf("%u", &toolsPtr->record);

  fseek(fPtr, (toolsPtr->record - 1) * sizeof(hardware), SEEK_SET);

  fwrite(toolsPtr, sizeof(hardware), 1, fPtr);
}

最佳答案

基本上,使用 stdio.h 函数,您无法真正删除任何内容。甚至没有 ftruncate (尽管我在 unistd.h 中有这样的函数)。因此,在使用这种类型的数据库时,您确实应该这样做:

为每条记录添加“已删除”字段。用它来确定应显示哪些记录。

为每条记录添加一个“id”字段。这样,同一条记录始终具有相同的 key 。

添加记录时,查找已删除的记录以重复使用,并在添加时使用新的“id”。

那么您就不必担心文件被截断。

或者

如果您有办法将文件截断到指定长度,您可以简单地加载最后一条记录(如果它不是您要删除的记录)并将其移动到您要删除的记录的位置,或者移动他们都返回 1 条记录以维持秩序。然后您可以将文件截断为所需的大小。

此外,您使用的模式“wb+”是错误的,因为它应该在打开文件时将其截断(什么也没有)。你真正想要的是模式“r+b”,它将打开它进行读写但不会截断。它没有说明如果文件不存在是否会创建该文件,因此您可能需要检查并使用“w+b”如果该文件尚不存在。

另外,在initializeRecords中,有一行char s[30] = ""。我不确定这是否真的允许,但它最多能做的就是初始化第一个字符(s[0] = '\0';)。更好(也更容易)的是像这样初始化toolsPtr:

fseek(fPtr, 0, SEEK_SET);
for (unsigned int i = 0; i < 10; ++i) {
    memset(toolsPtr, 0, sizeof(hardware));
    toolsPtr->record = i + 1;
    fwrite(toolsPtr, sizeof(hardware), 1, fPtr);
}

唯一真正的区别可能是文件将用零填充,而不是内存中的随机垃圾。如果您开始在文件上使用 hexdump 来查看出现问题时发生的情况,至少会产生影响。

关于c - 库存计划,可能的边界值问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54509203/

相关文章:

php - 自动递增文件名 PHP/MySQL

c - 如何检查 typedef 结构的内部 typedef 结构是否为 NULL?

c - 将程序的输出传输到 C 中的文件

c - 警告从不兼容的指针类型传递参数 1 of ‘fopen’

javascript - 为什么 C# 和 ECMAScript ISO 标准可以免费获得,而 C/C++ 却不能?

matlab - 将查找与结构一起使用

go - type 关键字在 go 中(究竟)做了什么?

C++动态库存系统--添加/删除项目

database - 将库存数据与产品数据保存在单独的表中

c - 简单的C程序,一些关于函数的问题