Makefiles и структуры в C - PullRequest
       0

Makefiles и структуры в C

1 голос
/ 01 марта 2020

РЕДАКТИРОВАТЬ: Чтобы попытаться облегчить жизнь тем добрым душам, которые пытаются мне помочь, вот пара ссылок, которые должны прояснить ситуацию:

Реплика перед сборкой файлов

Repl-makefile repl

Небольшая предыстория задания: мы должны взять программу, которую мы написали на прошлой неделе, и разбить отдельные функции на их собственные файлы и использовать Makefile для компиляции и ссылки и все такое. Это моя оригинальная программа (в основном она читает файл name number и сохраняет их в структуре, а затем использует аргументы командной строки для поиска имени).

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

struct _data {
  char* name;
  long number;
};

int SCAN(FILE *(*input)) {
  int lines = 0;
  char word[50];
  long num = 0;
  while (1) {
    fscanf(*input,"%s %ld",word, &num);
    lines++;
    if (feof(*input)) break;
  }
  return lines;
}

struct _data *LOAD(FILE *input, int size) {
  char* line = NULL;
  size_t len = 0;
  int i=0;
  rewind(input);
  struct _data *book = calloc(size,sizeof(struct _data));

  for (i = 0;i<size;i++) {
    getline(&line, &len, input);
    book[i].name = calloc(len+1,sizeof(char));
    strcpy(book[i].name,strtok(line," "));
    book[i].number = atoi(strtok(NULL, " "));
  }
  return book;
}

void SEARCH(struct _data *BlackBook, char *name, int size) {
  int i;
  for (i=0;i<size;i++) {
    if (strcmp(name,BlackBook[i].name) == 0) {
      printf("*******************************************\n");
      printf("The name was found at the %d entry.\n",i+1);
      printf("*******************************************\n");
      break;
    }
    //If we reach the end of the array and name was not found
    if (i == size-1) {
      printf("*******************************************\n");
      printf("The name was NOT found.\n");
      printf("*******************************************\n");
    }
  }
}

void FREE(struct _data *BlackBook, int size) {
  int i;
  for (i=0;i<size;i++){
    free(BlackBook[i].name);
  }
  free(BlackBook);
}

//MAIN DRIVER ===================
int main(int argv, char** argc) {

  int size;
  char* filename = "hw5.data";

  FILE *input = fopen(filename,"r");

  size = SCAN(&input);

  struct _data *phone_book = LOAD(input,size);

  fclose(input);

  //Check a name is given. If so, search
  if (argv < 2) {
    printf("*******************************************\n");
    printf("* You must include a name to search for.  *\n");
    printf("*******************************************\n");
  } else {
    SEARCH(phone_book, argc[1], size);
  }

  FREE(phone_book,size);
  return 0;
}

Когда я создаю свой make-файл Я могу заставить функции SCAN и LOAD работать должным образом. Но когда я пытаюсь поместить либо SEARCH, либо FREE в их собственные файлы, мой компилятор выходит из себя, и я получаю предупреждения вроде:

In file included from hw6-free.c:1:0:
hw6-free.h:9:18: warning: ‘struct _data’ declared inside parameter list
 void FREE(struct _data *BlackBook, int size);
                  ^
hw6-free.h:9:18: warning: its scope is only this definition or declaration, which is probably not what you want
hw6-free.c:3:18: warning: ‘struct _data’ declared inside parameter list
 void FREE(struct _data *BlackBook, int size) {
                  ^
hw6-free.c:3:6: error: conflicting types for ‘FREE’
 void FREE(struct _data *BlackBook, int size) {
      ^
In file included from hw6-free.c:1:0:
hw6-free.h:9:6: note: previous declaration of ‘FREE’ was here
 void FREE(struct _data *BlackBook, int size);
      ^
hw6-free.c: In function ‘FREE’:
hw6-free.c:6:5: error: invalid use of undefined type ‘struct _data’
     free(BlackBook[i].name);
     ^
hw6-free.c:6:19: error: dereferencing pointer to incomplete type ‘struct _data’
     free(BlackBook[i].name);
                   ^
Makefile:20: recipe for target 'hw6-free.o' failed
make: *** [hw6-free.o] Error 1

И, читая это, похоже на то, что мой программа принимает структуру в качестве аргумента моя главная проблема? Моя программа 'post-makefile' выглядит так:

#include "hw6-main.h"

int main(int argv, char** argc) {

  int size;
  char* filename = "hw5.data";

  FILE *input = fopen(filename,"r");

  size = SCAN(&input);

  struct _data *phone_book = LOAD(input,size);

  fclose(input);

  //Check a name is given. If so, search
  if (argv < 2) {
    printf("*******************************************\n");
    printf("* You must include a name to search for.  *\n");
    printf("*******************************************\n");
  } else {
    SEARCH(phone_book, argc[1], size);
  }

  FREE(phone_book,size);
  return 0;
}

А мой make-файл выглядит так:

DEP = hw6-scan.o hw6-load.o hw6-search.o hw6-free.o hw6-main.o
HDR = hw6-scan.h hw6-load.h hw6-search.h hw6-free.h hw6-main.h
NAME = output

all: $(NAME)

output: $(DEP) $(HDR)
    gcc $(DEP) $(HDR) -o $(NAME)

hw6-scan.o: hw6-scan.c
    gcc -c hw6-scan.c

hw6-load.o: hw6-load.c
    gcc -c hw6-load.c

hw6-search.o: hw6-search.c
    gcc -c hw6-search.c

hw6-free.o: hw6-free.c
    gcc -c hw6-free.c

hw6-main.o: hw6-main.c
    gcc -c hw6-main.c

clean:
    rm *.o *.gch *.out output testfile

Например, мои hw6-free.c и hw6-free.h выглядят так:

#include "hw6-free.h"

void FREE(struct _data *BlackBook, int size) {
  int i;
  for (i=0;i<size;i++){
    free(BlackBook[i].name);
  }
  free(BlackBook);
}

и

#include <stdio.h>
#include <stdlib.h>

void FREE(struct _data *BlackBook, int size);

соответственно.

И, наконец, я определил структуру в файле hw6-load.h вместе с прототипом функции. Это тоже проблема? Должен ли я определить это в другом месте?

Я очень сожалею о длинном посте, но я работаю над этим в течение 10 часов, и я собираюсь бросить свой компьютер со скалы.

СПАСИБО ЗА ВЫПОЛНЕНИЕ ПОМОЩИ!

Ответы [ 3 ]

3 голосов
/ 01 марта 2020

Я не уверен, что ты сделал. Но сделаем шаг назад: помните о разнице между декларациями и определениями . Объявление показывает переменную или сигнатуру функции, но не создает никаких переменных этого типа и не реализует эту функцию. определение объявляет новую переменную (некоторого типа) или реализует функцию. объявление типа просто утверждает, что он существует (в основном). определение типа показывает его структуру и члены.

Итак, определение вашего типа будет:

struct _data {
  char* name;
  long number;
};

И объявление функции будет :

void FREE(struct _data *BlackBook, int size);

и определение функции будет следующим:

void FREE(struct _data *BlackBook, int size) {
  int i;
  for (i=0;i<size;i++){
    free(BlackBook[i].name);
  }
  free(BlackBook);
}

Итак, вот правила:

  1. Только #include заголовочные файлы в других файлах никогда не #include исходные файлы.
  2. Если тип требуется более чем в одном файле, поместите его definition в файл заголовка и #include этот файл заголовка во всех исходные файлы, которые используют тип.
  3. Если функция используется более чем в одном файле, поместите объявление этой функции в файл заголовка и #include этот файл заголовка во всех файлах. исходные файлы, которые используют функцию, включая исходный файл, содержащий определение функции.

Если вы будете следовать этим правилам, вы никогда не столкнетесь с дублирующимися определениями во время ссылки: вы не можете иметь повторяющиеся определения, если вы связываете каждый исходный файл только по одному time и ни один из включенных файлов не содержит определения.

Объявления типов могут быть полезны, и нарушение некоторых из этих правил может быть полезным, но для работы, которую вы выполняете, это, вероятно, не стоит беспокоиться.

3 голосов
/ 02 марта 2020

Это подробное объяснение деталей. Это может быть слишком много, но если вы продолжите читать, вы можете получить более глубокое понимание языка, чем если бы вы просто научились писать код, с которым у вашего компилятора все в порядке.


Вы используете struct до того, как объявите его . Имейте в виду, что #include - это просто причудливый способ сказать вашему компилятору: «Пожалуйста, вставьте содержимое этого .h файла в это место». Результат этого вставки должен читаться как действительный код для компилятора.

Это правда, что сообщение об ошибке компилятора выглядит немного странно. Это, конечно, не говорит: «Вы использовали struct _data до того, как объявили об этом». Причина этого заключается в том, что структуры неявно объявляются при их первом использовании. Таким образом, если вы объявите переменную с struct foo* bar; и компилятор никогда не видел struct foo раньше, он сразу же будет считать struct foo объявленным, и переменная-указатель на этот struct foo неизвестного размера и формы будет определены. Аналогично, когда ваш компилятор видит объявление функции

void foo(struct bar *baz);

, он видит, что он не знает struct bar, и неявно объявляет его. Поскольку это объявление структуры происходит внутри объявления функции, результирующий тип объявляется локальным по отношению к объявлению под рукой. Таким образом, вызывающий код не может передать аргумент правильного типа в эту функцию или даже реализовать функцию в отдельном операторе . Такое объявление функции всегда бесполезно. Вот что означает предупреждающий текст «его область действия - только это определение или объявление, что, вероятно, не то, что вам нужно»: авторы компилятора знали, что такое объявление - чушь собачья, но в отношении стандарта это законно C поэтому они его компилируют, но предупреждают об этом.


Хорошо, давайте перейдем к фактическому сообщению об ошибке. Как говорит вам компилятор, struct _data был объявлен только для объявления функции под рукой. Когда ваш компилятор позже увидит реализацию функции, он наткнется на необъявленный struct _data во второй раз. Опять же, он неявно объявляет локальный тип структуры, который отличается от ранее неявно объявленного типа. Поскольку эти два неявно объявленных типа различны, так же как и сигнатуры объявленных функций. Однако C требует, чтобы функция могла иметь только одну подпись, поэтому компилятор выдает ошибку «конфликтующие типы для« FREE »».

Вы можете попробовать это с помощью этого простого кода:

void foo(struct bar* baz);    //warning: local declaration of `struct bar`
void foo(struct bar* baz);    //repeated warning + conflicting types error

Итак, как это исправить?

Просто. Объявите ваш struct, прежде чем использовать его. Таким образом вы избежите его неявного объявления. Следующий код прекрасно компилируется:

struct bar;    //global declaration of `struct bar`
void foo(struct bar* baz);    //`struct bar` is known and the global declaration is used
void foo(struct bar* baz);    //same as above, because this uses the same global declaration of `struct bar`, this redeclaration of `foo()` is ok

Объявление struct _data входит в заголовочный файл, который объявляет функции, использующие struct _data в качестве аргументов.


Idiomati c объявлений

Обычно типы объявляются с typedef. Это позволяет коду пропускать ключевое слово struct при объявлении переменных. Это принимает одну из двух идиоматических c форм:

  • Чтобы иметь тип с членами publi c (чистые данные, без объекта в ОО-смысле), поместите struct определение в заголовок:

    typedef struct foo    //`struct foo` is declared implicitly here
    {    //it is also defined (= inner details are given) right here
        int bar;    //its member variables are defined
    } baz;    //this concludes the typedef, giving `struct foo` a second name
    //`struct foo` and `baz` are now equivalent.
    

    Обычно два имени будут одинаковыми или очень похожими, поэтому очищенное определение выглядит следующим образом:

    typedef struct foo {
        int bar;
    } foo;
    
    //declarations of the functions that use `struct foo`
    ...
    
  • Если тип является объектом, который должен хранить свои элементы данных при себе, объявление и определения разделяются следующим образом:

    Внутри foo.h:

    typedef struct foo foo;    //declare that `struct foo` == `foo` exists, but don't give details
    
    //declare the functions working on a `foo`
    void foo_bim(foo* me);
    void foo_bam(foo* me, ...);
    ...
    

    Внутри foo. c:

    #include "foo.h"    //so that the compiler may check that the function declarations in the header agree with the implementations in this file
    
    struct foo {    //define the size and shape of `struct foo` == `foo`
        int bar;
    };
    //now only this file knows how a `struct foo` actually looks like
    
    //implement the member functions of `foo`
    void foo_bim(foo* me) {
        ...
    }
    
    void foo_bam(foo* me, ...) {
        ...
    }
    

Обратите внимание, что typedef ... non-struct-name; является необязательным в обоих случаях, и есть довольно много программистов, которые хотят видеть struct ключевое слово везде, где используется struct (как у определенного мистера Торвальдса). Эти программисты просто пропускают часть typedef ... non-struct-name;, в противном случае они используют описанные выше идиомы таким же образом. Я описал здесь полнофункциональную версию, чтобы вы не удивились, когда впервые увидели конструкцию typedef.

1 голос
/ 01 марта 2020

Попробуйте поместить заголовок объявления структуры поверх файлов, которые его используют, и убедитесь, что вы импортировали файл, содержащий объявление структуры.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...