搜尋

首頁  >  問答  >  主體

c++编译遇到重复定义

一直对编译这个事情不是太明白,想好好学习一下,今天自己写东西试验,遇到了这么个问题,想请教一下,先贴代码

// test.h
#ifndef JUST_FOR_TEST_H_
#define JUST_FOR_TEST_H_

struct Test {
  static int i;
  void t();
};

int Test::i = 0;

#endif
// test.cpp 
#include "test.h"

void Test::t() { }
// main.cpp
#include "test.h"

int main() {
  Test t;
  t.t();
}

本来觉得这个是很简单的,但是链接的时候报了错,提示是

ld: 1 duplicate symbol for architecture x86_64

将test.h中静态成员的定义放到cpp文件中就好了,但是我很不理解,不是使用了头文件保护吗?这样不是已经确保了一个头文件在一个工程中只会被包含一次吗?是我哪里的理解出了偏差了吗?希望各位高手可以指点一下,本人菜鸡,不是学计算机出身的,所以问的问题可能很傻,如果可以的话希望可以讲的详细一点,如果可以介绍两本学习的书就更好了,先谢过了!

天蓬老师天蓬老师2803 天前546

全部回覆(1)我來回復

  • 巴扎黑

    巴扎黑2017-04-17 15:41:16

    短答案:

    headguard只能防止頭檔被二次包含。你編譯時的問題是,i在不同的編譯單元中被定義。

    多數ide編譯時會將所有的原始檔分別編譯成目標文件,最後由連接器將目標文件連接起來。目標檔案之間的連接關係是由對應來源檔案中的include來推斷的。頭檔不參與編譯。此處IDE產生的編譯腳本差不多是clang++ main.cpp test.cpp。當然除此之外還有很多工程設定裡的編譯選項。

    你的例子中,編譯器會編譯test.cpp和main.cpp,產生目標檔。然後連接器會將這兩個目標檔案連接起來。由於靜態成員變數i定義在頭檔中,並且兩個原始檔都包含了這個頭檔。結果就是i在兩個編譯單元中分別被定義了一次。最後連接時就會出現連線錯誤。

    長答:

    你遇到的連結錯誤其實是因為違反ODR引起的。

    ODR約定:

    Only one definition of any variable, function, class type, enumeration type, or template is allowed in any one translation unit (some of these may have multiple declarations, but only unit (some of these may have multiple declarations, but only one definition is allowed).

    One and only one definition of every non-inline function or variable that is odr-used (see below) is required to appear in the entire program (including any standard and user-defined libraries).

    但除此之外還有很多的特例。這裡就不列舉了(主要因為是我列不全…)。

    其中有三個概念很重要:translation unit, definition和program.

    translation unit是編譯單元,總共有幾個編譯單元由編譯指令決定。編譯單元的編譯結果稱為目標檔。 definition是定義,在這個例子中是靜態成員變數int Test::i = 0;的定義,根據ODR,這個定義在整個程式中只能出現一次。 iprogram是程序,程式由眾多的目標檔案連結而成。

    編譯指令:

    clang++ main.cpp test.cpp -o main

    此次編譯一共有兩個編譯單元main.cpp和test.cpp。他們會被分別編譯成目標檔。

    是預編譯宏,他會引入對應檔的內容,並且所有的預編譯指令都會在原始碼被編譯前執行。也就是說編譯單元"test.cpp"的內容(或說clang編譯器收到的程式碼)為:#include

    // test.cpp
    // test.h
    struct Test {
      static int i;
      void t();
    };
    
    int Test::i = 0;
    
    void Test::t() { }

    編譯單元"main.cpp"的內容為:

    // main.cpp
    // test.h
    struct Test {
      static int i;
      void t();
    };
    
    int Test::i = 0;
    
    int main() {
      Test t;
      t.t();
    }

    所有的巨集都執行完畢,註解會被編譯器忽略。

    這兩個編譯單元會分別編譯成目標文件,比方說test.o和main.o。編譯工作結束。然後編譯器會將這兩個目標檔test.o和main.o連結成一個程序,

    違反ODR就是在這個連結時發生的

    如果int Test::i = 0;的定義在test.cpp。那麼在main.cpp中就不會出現這個定義,也就沒有違反ODR。

    回覆
    0
  • 取消回覆