0

There is only one definition of the name 'codes' in all of my files, although I still get an error. I tried using pragma once and ifndef, but to no avail.

Here is the error I'm getting:

marcin@marcin-VirtualBox:~/Pulpit/INA/Wstęp/Laby/Lista 4/zadanie 2$ make
gcc -g checkCode.o getDigitBase10.o main.o populateCodeList.o getNextGuess.o -o solve -lm
/usr/bin/ld: getDigitBase10.o:/home/marcin/Pulpit/INA/Wstęp/Laby/Lista 4/zadanie 2/funkcje.h:10: multiple definition of `codes'; checkCode.o:/home/marcin/Pulpit/INA/Wstęp/Laby/Lista 4/zadanie 2/funkcje.h:10: first defined here
/usr/bin/ld: main.o:/home/marcin/Pulpit/INA/Wstęp/Laby/Lista 4/zadanie 2/funkcje.h:8: multiple definition of `codes'; checkCode.o:/home/marcin/Pulpit/INA/Wstęp/Laby/Lista 4/zadanie 2/funkcje.h:10: first defined here
/usr/bin/ld: populateCodeList.o:/home/marcin/Pulpit/INA/Wstęp/Laby/Lista 4/zadanie 2/funkcje.h:8: multiple definition of `codes'; checkCode.o:/home/marcin/Pulpit/INA/Wstęp/Laby/Lista 4/zadanie 2/funkcje.h:10: first defined here
/usr/bin/ld: getNextGuess.o:/home/marcin/Pulpit/INA/Wstęp/Laby/Lista 4/zadanie 2/funkcje.h:8: multiple definition of `codes'; checkCode.o:/home/marcin/Pulpit/INA/Wstęp/Laby/Lista 4/zadanie 2/funkcje.h:10: first defined here
collect2: error: ld returned 1 exit status
make: *** [Makefile:10: all] Błąd 1

And here are all of my files:

//funkcje.h
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include <stdlib.h>
#pragma once
#define SIZE 1296
 
#ifndef codes
int codes[SIZE];
#endif
void populateCodeList(int codes[]);
int getDigitBase10(int n, int digit);
struct results checkCode(int trueCode,int guessCode);
int getNextGuess(int codes[]);
struct results{
    int white;
    int red;
};
 
//checkCode.c
 
#include "funkcje.h"
 
struct results checkCode(int trueCode,int guessCode){
    struct results feedback;
    feedback.red = 0;
    feedback.white = 0;
    if(getDigitBase10(trueCode, 1) == getDigitBase10(guessCode, 1)){
        feedback.red++;
    } else if(getDigitBase10(trueCode, 1) == getDigitBase10(guessCode, 2)){
        feedback.white++;
    } else if(getDigitBase10(trueCode, 1) == getDigitBase10(guessCode, 3)){
        feedback.white++;
    } else if(getDigitBase10(trueCode, 1) == getDigitBase10(guessCode, 4)){
        feedback.white++;
    } 
 
    if(getDigitBase10(trueCode, 2) == getDigitBase10(guessCode, 2)){
        feedback.red++;
    } else if(getDigitBase10(trueCode, 2) == getDigitBase10(guessCode, 1)){
        feedback.white++;
    } else if(getDigitBase10(trueCode, 2) == getDigitBase10(guessCode, 3)){
        feedback.white++;
    } else if(getDigitBase10(trueCode, 2) == getDigitBase10(guessCode, 4)){
        feedback.white++;
    } 
 
    if(getDigitBase10(trueCode, 3) == getDigitBase10(guessCode, 3)){
        feedback.red++;
    } else if(getDigitBase10(trueCode, 3) == getDigitBase10(guessCode, 1)){
        feedback.white++;
    } else if(getDigitBase10(trueCode, 3) == getDigitBase10(guessCode, 2)){
        feedback.white++;
    } else if(getDigitBase10(trueCode, 3) == getDigitBase10(guessCode, 4)){
        feedback.white++;
    } 
 
    if(getDigitBase10(trueCode, 4) == getDigitBase10(guessCode, 4)){
        feedback.red++;
    } else if(getDigitBase10(trueCode, 4) == getDigitBase10(guessCode, 1)){
        feedback.white++;
    } else if(getDigitBase10(trueCode, 4) == getDigitBase10(guessCode, 2)){
        feedback.white++;
    } else if(getDigitBase10(trueCode, 4) == getDigitBase10(guessCode, 3)){
        feedback.white++;
    }
    return feedback;
}
//getDigitBase10.c
#include "funkcje.h"
 
int getDigitBase10(int n, int digit){
 
    for(int i=1; i< digit; i++){
        n = n/10;
    }
 
    return n%10;
}
//getNextGuess.c
#include "funkcje.h"
 
int getNextGuess(int codes[]){
    while(1){
        int i = 0;
        if(codes[i] != 9999){
            return codes[i];
        }
    }
}
//populateCodeList.c
#include "funkcje.h"
 
void populateCodeList(int codes[])
{
    int i = 0;
    for(int a=1; a<=6; a++){
        for(int b=1; b<=6; b++){
            for(int c=1; c<=6; c++){
                for(int d=1; d<=6; d++){
                    codes[i++] = a*1000 + b*100 + c*10 + d;
                }
            }
        }
    }
}
//main.c
#include "funkcje.h"
 
int main(void){
    int turn = 1;
    bool won = false;
 
    int code = 1234; //Zmienić tę linijkę aby zmienić kod
    int currentGuess = 1122;
 
    populateCodeList(codes);
 
    while(!won){
 
    struct results currentResults = checkCode(code,currentGuess); // Trzymaj wyniki tury w currentResults
    printf("Ze strzałem %d, wyniki są następujące:\nRed: %d\nWhite: %d\n",currentGuess, currentResults.red, currentResults.white); // Powiedz jakie są wyniki tej tury
 
    if(currentResults.red == 4){    // Jeśli są 4 czerwone, to znaczy że komputer wygrał.
        printf("Wygrałem! Twój kod to %d",currentGuess);
        break;
    }
 
    for(int i=0;i<SIZE;i++){ // Usuń wszystkie kody, których wyniki nie równają się wynikom z tej tury
        if(checkCode(currentGuess,codes[i]).red != checkCode(code,currentGuess).red && 
            checkCode(currentGuess,codes[i]).white != checkCode(code,currentGuess).white){
            codes[i] = 9999;
        }
    } 
 
    currentGuess = getNextGuess(codes); //Weź najmniejszy nie-usunięty kod jako następny strzał
 
        turn++;
        if(turn>8){
            printf("Czas mi się skończył i nie odgadłem twojego kodu.");
            break;
        }
    }
    return 0;
}

I tried to use pragma once to avoid the linker error and tried to surround the codes definition in the ifndef order but it still produces the same error as before. While copying and pasting everything to the same file, no errors are thrown.

Meduza3
  • 1
  • 3
  • `codes` is defined in a header file (funkcje.h). This header is included in multiple c files. Each c file will have this definition and hence the multiple definition error you get by the linker when the object files from these c files are linked. – wohlstad Jan 03 '23 at 09:33
  • How can I make it so all files have access to the header but the error does not show up? – Meduza3 Jan 03 '23 at 09:37
  • 1
    Surely this is a FAQ. Does anyone know of a good canonical dupe for "do not declare variables in header files"? – Lundin Jan 03 '23 at 09:37
  • 1
    @Meduza3 Declare `codes` in `funkcje.c` as `static int codes[SIZE];` and then provide setter/getter function declarations through the header file to change that variable's contents. – Lundin Jan 03 '23 at 09:39
  • 1
    @Lundin "do not declare variables in headers" would be bad advice in a canonical. Not to define however.... – Yunnosch Jan 03 '23 at 09:48
  • 1
    Mmhm, define. Anyways, someone found a good dupe. – Lundin Jan 03 '23 at 10:02
  • Global variables are bad. Their integrity becomes difficult to maintain as the project becomes more complex... However, since you seem to be writing a small program for your own amusement, 1) copy the line unaltered from `funkcje.h` into (I recommend) `populateCodeList.c`... 2) in the header file, change `int codes[SIZE];` to `extern int codes[SIZE];`... 3) erase your attempts to fix the issue in the header. 4) change the value of `SIZE` from the magic number 1296 to `(6*6*6*6)` to give the reader a chance to understand what the value signifies... 5) Since it's global, don't pass it around. – Fe2O3 Jan 03 '23 at 10:02
  • @wohlstad: `int codes[SIZE];` at file scope is not a definition as the C standard defines it. For example, if a translation unit contains `int codes[SIZE];` and later `int codes[SIZE] = { 3, 4 };`, the latter is a definition, the former is only a declaration, and there is no multiple-definition error. The standard says that `int codes[SIZE];` is a *tentative definition*, but, despite that term, it classifies it as a declaration, not a definition. If there is no definition in the unit, then a tentative definition causes a definition to be created, although it remains technically a declaration. – Eric Postpischil Jan 03 '23 at 12:50
  • @EricPostpischil thanks for the clarification, I was not aware of this. – wohlstad Jan 03 '23 at 12:52

1 Answers1

3

At file scope, int codes[SIZE] is a special kind of declaration called a tentative definition. In spite of its name, it is not a definition. However, it can cause a definition to be created.

A clean way to declare and define an object that is used in multiple translation units is to declare it with extern in a header that is included in each unit that uses the object by name:

extern int codes[SIZE];

and to define the object in the source file of one translation unit:

int codes[SIZE] = { 0 };

The tentative definition form, without extern, has been in somewhat common use, particularly with GCC which treated it as a “common” symbol, described below. However, GCC’s default behavior changed in version 10, with the result that this common practice now causes multiple-definition errors. You can get the old behavior with the -fcommon switch, but generally programmers should switch to the explicit declaration form, extern codes[SIZE];.

This reason a file-scope declaration of int codes[SIZE]; is a tentative definition lies in the history of C development. C was not completely planned and designed in advance. It developed through experiments and different people in different places implementing things differently. When the C committee standardized C, they had to deal with different practices and implementations. One common practice was that of declarations such as int i; in multiple units that were intended to create a single i. (This behavior was inherited from FORTRAN, which had common objects as a similar feature.)

To accommodate this, the committee described int i; at file scope as a special kind of declaration, a tentative definition. If there is a regular definition in the same translation unit that defines the same identifier, the tentative definition acts as a plain declaration, not a definition. If there is no regular definition, the compiler (or other part of C implementation) creates a definition for the identifier as if it had been initialized with zero.

The C standard leaves reconciling of multiple tentative definitions to each C implementation; it does not define the behavior when int i; is used in multiple translation units. Prior to version 10, the default behavior of GCC was to use the “common symbol” behavior; multiple tentative definitions would be reconciled to a single definition when linking. (To support this, the compiler marks tentative definitions differently from regular definitions when creating object modules, so the linker knows which is which.) In version 10, the default changed, and GCC now treats the definitions resulting from tentative definitions as regular symbols instead of common symbols. That is why you get the “multiple definition” error from having int codes[SIZE]; in the header file.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312