簡體   English   中英

多定義錯誤C ++

[英]multiple definition error c++

我的“ Headers.h”文件包含基本的c ++標頭

#include <iostream>
#include <cstring>
// and many header files.

為文件存在檢查寫了一個函數定義,並將其保存在“ common_utility.h”中-ifFileExist()

common_utility.h

bool ifFileExist()
{
   // ... My code
}

為A類classA.h寫代碼

class A
{
// Contains class A Declarations.

};

A類

// Contains
#include "Headers.h"
#include "common_utility.h"
#include "classA.h"

// class A Method definition

BI類的代碼正在使用B類中的A類。

B類

 class B
    {
// Contains class A Declarations.
}

classB.cpp

// Contains
#include "Headers.h"
#include "common_utility.h"
#include "classA.h"
#include "classB.h"

// class B Method definition
// calling the function ifFileExist() in class B also.

為主程序main.cpp編寫代碼

// Contains
#include "Headers.h"
#include "common_utility.h"
#include "classA.h"
#include "classB.h"

// I am using class A and Class B in main program
// calling the function ifFileExist() in Main program also.

當我將整個程序編譯為

g ++ -std = c ++ 0x classA.cpp classB.cpp main.cpp -o main

我收到以下錯誤。

在函數ifFileExist()': classB.cpp:(.text+0x0): multiple definition of :首先在此處定義

因此,我將Headers.h中的ifFileExist()函數清除為extern。

extern bool ifFileExist();

但是我仍然遇到相同的錯誤。

我在每個.cpp文件中都包含“ Headers.h”。 該文件包含基本的c ++庫。 但是我沒有收到該頭文件的任何多個定義錯誤。 但是只有在我自己的函數中,我才收到錯誤“多重定義”。

我想在需要使用“ common_utility.h”文件時使用它。 如果我不需要在主程序中使用common_utility函數,則簡單地不應該包含它。

我希望我的程序在以下所有情況下運行。

g ++ -std = c ++ 0x classA.cpp main.cpp -o main
g ++ -std = c ++ 0x classB.cpp> main.cpp -o main
g ++ -std = c ++ 0x classA.cpp classB.cpp main.cpp -o main

在任何情況下我都不會出現多個定義錯誤。 我現在應該怎么辦?

由於找不到該問題的完整(我認為)重復副本,因此,我將寫一個(希望)權威且完整的答案。

什么是一個定義規則,為什么我要關心

一個定義規則(通常稱為ODR)是一條規則,指出(簡化)程序中使用的任何實體(非正式術語)都應定義一次,並且只能定義一次。 多次定義的實體通常會導致編譯或鏈接器錯誤,但有時可能會被編譯器檢測不到,並導致難以跟蹤的錯誤。

我不會在這里正式定義實體 ,但是可以將其視為函數,變量或類。 在繼續之前,應該非常清楚地了解C ++中定義聲明之間的區別,因為雖然禁止了雙重定義,但通常不可避免地會出現雙重聲明。

定義與聲明

代碼中使用的每個實體都應在給定的轉換單元中聲明 (轉換單元通常是cpp源文件以及包含在其中的所有頭文件,直接或通過其他頭文件間接)。 實體的聲明方式因實體本身而異。 參見下文,了解如何聲明不同類型的實體。 實體通常在頭文件中聲明。 由於大多數復雜的應用程序中都包含一個以上的轉換單元(一個以上的cpp文件),並且不同的cpp文件通常包含相同的標頭,因此一個應用程序可能會為使用的許多實體提供多個聲明。 就像我上面說的那樣,這不是問題。

應用程序中使用的每個實體都必須定義一次,並且只能定義一次。 “應用程序”一詞在這里使用得比較寬松-例如,庫(靜態和動態)中可以包含未定義的實體(此時通常稱為符號),而鏈接為使用動態庫的可執行文件可以也有未定義的符號。 相反,我指的是應用程序是最終運行的東西 ,在所有庫都已靜態或動態鏈接到其中並解析了符號之后。

還值得注意的是,每個定義也都充當聲明,也就是說,每當定義某些內容時,您也就聲明了同一件事。

與聲明一樣,定義實體的方式因實體類型而異。 這是一種可以根據實體的類型聲明/定義3種基本類型的實體的方法-變量,類和函數。

變數

使用以下結構聲明變量:

extern int x;

這聲明了變量x。 它沒有定義! 下面的代碼將被編譯為OK,但是嘗試在沒有任何其他輸入文件的情況下鏈接它(例如,使用g++ main.cpp )將由於未定義符號而產生鏈接時錯誤:

extern int x;
int main() {
    return x;
}

以下代碼定義了變量x:

int x;

如果將這一行放入文件x.cpp中,並且使用g++ x.cpp main.cpp -o test從上面將文件與main.cpp編譯/鏈接在一起,則它將毫無問題地進行編譯和鏈接。 您甚至可以運行生成的可執行文件,如果要在運行可執行文件后檢查退出代碼,您會發現它為0。(因為全局變量x會默認初始化為0)。

職能

通過提供其原型來聲明函數。 典型的函數聲明如下所示:

double foo(int x, double y);

此結構聲明一個函數foo ,返回double並接受兩個參數-一個類型為int ,另一個類型為double 該聲明可以出現多次。

以下代碼定義了上述foo

void foo(int x, double y) {
    return x * y;
}

定義在整個應用程序中只能出現一次。

函數定義對變量定義有一個額外的怪癖。 如果將foo上述定義放入頭文件foo.h ,該文件將依次包含在兩個cpp文件1.cpp2.cpp ,這兩個文件與g++ 1.cpp 2.cpp -o test一起編譯/鏈接在一起您將遇到一個鏈接器錯誤,說foo()被定義了兩次。 使用以下形式的foo聲明可以防止這種情況:

inline void foo(int x, double y) {
    return x * y;
}

請注意在此inline 它告訴編譯器的是foo可以包含在多個.cpp文件中,並且這種包含不應產生鏈接器錯誤。 編譯器有幾種實現方法,但可以依靠它來完成任務。 請注意,在同一翻譯單元中兩次定義都將是一個錯誤! 例如,以下代碼將產生編譯器錯誤

inline void foo() { }
inline void foo() { }

值得注意的是,該類中定義的任何類方法都是隱式內聯的,例如:

class A {
public:
    int foo() { return 42; }
};

這里A :: foo()是inline定義的。

班級

通過以下構造聲明類:

class X;

上面的聲明聲明了 X類(此時X正式稱為不完整類型 ),以便在不需要有關其內容的信息(例如其大小或成員)時可以使用它。 例如:

X* p; // OK - no information about class X is actually required to define a pointer to it
p->y = 42; // Error - compiler has no idea if X has any member named `y`

void foo(X x); // OK - compiler does not need to generated any code for this

void foo(X x) { } // Error - compiler needs to know the size of X to generate code for foo to properly read it's argument
void bar(X* x) { } // OK - compiler needs not to know specifics of X for this

類的定義是眾所周知的,並遵循以下結構:

class X {
   public:
   int y;
};

這樣就定義了一個類X,現在可以在任何上下文中使用它。 重要說明-類定義在每個trannlation單元中必須唯一,但在每個應用程序中不必唯一。 也就是說,每個轉換單元只能定義一次X,但是它可以在鏈接在一起的多個文件中使用。

如何正確遵守ODR規則

每當在結果應用程序中多次定義同一實體時,就會發生所謂的ODR沖突 在大多數情況下,鏈接器會看到違規並提出投訴。 但是,在某些情況下,違反ODR的操作不會中斷鏈接,而是會導致錯誤。 例如,當將定義全局變量X的相同.cpp文件放入應用程序和動態庫中時,可能會發生這種情況,並按需加載(使用dlopen )。 (您確實花了幾天的時間來嘗試跟蹤由於此而發生的錯誤。)

違反ODR的更常見原因是:

同一實體在同一范圍內的同一文件中定義了兩次

int x;
int x; // ODR violation

void foo() {
   int x;
} // No ODR violation, foo::x is different from x in the global scope

預防措施 :請勿這樣做。

同一實體定義了兩次(應在聲明時進行聲明)

(in x.h)
int x;

(in 1.cpp)
#include <x.h>
void set_x(int y) {
   x = y;
}

(in 2.cpp)
#include <x.h>
int get_x() {
    return x;
}

盡管以上代碼的智慧充其量是可疑的,但它可以說明ODR規則。 在上面的代碼中,變量x應該在兩個文件1.cpp和2.cpp之間共享,但是編碼不正確。 相反,代碼應如下所示:

(in x.h)
extern int x; //declare x

(in x.xpp)
int x; // define x

// 1.cpp and 2.cpp remain the same

預防知道自己在做什么。 在聲明實體時聲明它們,不要定義它們。 如果在上面的示例中,我們將使用function而不是變量,如下所示:

(in x.h)
int x_func() { return 42; }

我們將有一個可以通過兩種方式解決的問題(如上所述)。 我們可以使用inline函數,也可以將定義移至cpp文件:

(in x.h)
int x_func();

(in x.cpp)
int x_func() { return 42; } 

相同的頭文件包含兩次,導致相同的類定義了兩次,這很有趣。 想象一下,您有以下代碼:

(in a.h)
class A { };

(in main.cpp)
#include <a.h>
#include <a.h> // compilation error!

上面的代碼很少以書面形式出現,但是通過中間層包含兩次相同的文件是很容易的:

(in foo.h)
#include <a.h>

(in main.cpp)
#include <a.h>
#include <foo.h>

預防傳統的解決方案是使用所謂的include Guards ,即特殊的預處理程序定義,可以防止雙重包含。 在這方面,ah應該重做如下:

(in a.h)
#ifndef INCLUDED_A_H
#define INCLUDED_A_H

class A { };

#endif

上面的代碼將防止將ah多次INCLUDED_A_H在同一個翻譯單元中,因為INCLUDED_A_H將在首次包含之后定義,並且將在所有后續代碼中失敗#ifndef

一些編譯器公開了其他控制包含的方法,但是到目前為止,包括防護仍然是在不同的編譯器之間統一地進行控制的方法。

在實際編譯源代碼之前,編譯單元是從.cpp文件生成的。 這基本上意味着所有預處理器指令被計算:所有#include將與包含文件的內容內容替換,所有#define倒是值將與相應的表達式被取代,所有#if 0 ... #endif將被移除,等等因此,按照您的情況進行此步驟之后,您將獲得兩段C ++代碼,而沒有任何預處理程序指令,這兩個指令都具有相同的功能bool ifFileExist()定義,這就是為什么您會遇到此多定義錯誤的原因。

快速解決方案是將其標記為inline bool ifFileExist() 基本上,您要求編譯器用函數本身的內容替換所有相應的函數調用。

另一種方法是將函數的聲明保存在common_utility.h並將定義移至common_utility.cpp

最簡單的解決方法是:insert關鍵字inlineinline bool ifFileExist()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM