• C++:重定义:符号重定义:变量重定义


    概述:在上一篇我们知道 通过 #ifndef....#defin....#endif , 这个解决头文件重复包含的问题

    C++:重定义:class类型重定义_hongwen_yul的博客-CSDN博客

    避免头文件的重复包含可以有效的避免变量的重复定义,其实不光变量可以避免重复定义,也可以避免函数和类、结构体的重复定义。但是避免头文件的重复包含是否一定可以避免 变量、函数、类、结构体的重复定义了 ?答案当然是否定的,下面我们通过GCC工具来探究 “C++ 预处理---》编译----》链接” 这几个过程。

    1:GCC下载,教程很详细,自己看下就可以了

    Windows下GCC安装和使用_丸子爱学习!的博客-CSDN博客_windows gcc

    gcc 常见的命令,参照下面这篇文章

    C++:GCC编译:GCC编译C++程序分步流程_hongwen_yul的博客-CSDN博客

    2:  #ifndef....#defin....#endif 解决预编译阶段头文件重复包含

    2.1 :头文件在没有添加  #ifndef....#defin....#endif情况下

    1. a.h
    2. int A = 2;
    1. b.h
    2. #include
    3. #include"a.h"
    4. void fb();
    5. b.cpp
    6. #include"b.h"
    7. void fb() {
    8. printf("%d", A+1);
    9. }
    1. c.h
    2. #pragma once
    3. #include
    4. #include"a.h"
    5. void fc();
    6. c.cpp
    7. #include"c.h"
    8. void fc() {
    9. printf("%d", A + 2);
    10. }
    1. main.cpp
    2. #include
    3. #include"b.h"
    4. #include"c.h"
    5. #include
    6. int main() {
    7. fb();
    8. fc();
    9. return 0;
    10. }
    11. // a.h b.h c.h 这三个头文件没有添加 #ifndef....#defin....#endif 情况下
    12. // 通过 gcc -E main.cpp 预编译结果
    13. # 2 "main.cpp" 2
    14. # 1 "b.h" 1
    15. # 1 "a.h" 1
    16. # 1 "a.h"
    17. int A = 2; // 第一次定义变量 A
    18. # 3 "b.h" 2
    19. void fb();
    20. # 3 "main.cpp" 2
    21. # 1 "c.h" 1
    22. # 1 "a.h" 1
    23. int A = 2; // 第二次定义变量 A
    24. # 3 "c.h" 2
    25. void fc();
    26. # 4 "main.cpp" 2
    27. int main() {
    28. fb();
    29. fc();
    30. return 0;
    31. }
    32. // 很明显 预编译之后的结果:main.cpp 会定义两次 变量 A ,所以通过 gcc -C main.cpp 就会出现变量重复定义问题
    33. $ gcc -C main.cpp
    34. In file included from c.h:2:0,
    35. from main.cpp:3:
    36. a.h:1:5: error: redefinition of 'int A'
    37. int A = 2;
    38. ^
    39. In file included from b.h:2:0,
    40. from main.cpp:2:
    41. a.h:1:5: note: 'int A' previously defined here
    42. int A = 2;
    43. ^

    2.2 头文件添加  #ifndef....#define....#endif 或者 #pragma once

    可以看到main.cpp 编译后没有任何影响,从而避免了重复包含的问题

    1. a.h
    2. #pragma once
    3. int A = 2;
    4. ===================================================================================
    5. b.h
    6. #pragma once
    7. #include
    8. #include"a.h"
    9. void fb();
    10. b.cpp
    11. #include"b.h"
    12. void fb() {
    13. printf("%d", A+1);
    14. }
    15. ==========================================================================================
    16. c.h
    17. #pragma once
    18. #include
    19. #include"a.h"
    20. void fc();
    21. c.cpp
    22. #include"c.h"
    23. void fc() {
    24. printf("%d", A + 2);
    25. }
    26. =========================================================================================
    27. main.cpp
    28. #include
    29. #include"b.h"
    30. #include"c.h"
    31. #include
    32. int main() {
    33. fb();
    34. fc();
    35. return 0;
    36. }
    1. // gcc -E main.cpp 预编译结果
    2. # 2 "main.cpp" 2
    3. # 1 "b.h" 1
    4. # 1 "a.h" 1
    5. # 2 "a.h"
    6. int A = 2;
    7. # 4 "b.h" 2
    8. void fb();
    9. # 3 "main.cpp" 2
    10. # 1 "c.h" 1
    11. void fc();
    12. # 4 "main.cpp" 2
    13. int main() {
    14. fb();
    15. fc();
    16. return 0;
    17. }
    18. // gcc -C -S main.cpp 编译的结果
    19. .file "main.cpp"
    20. .section .rdata,"dr"
    21. __ZStL19piecewise_construct:
    22. .space 1
    23. .globl _A
    24. .data
    25. .align 4
    26. _A:
    27. .long 2
    28. .def ___main; .scl 2; .type 32; .endef
    29. .text
    30. .globl _main
    31. .def _main; .scl 2; .type 32; .endef
    32. _main:
    33. LFB935:
    34. .cfi_startproc
    35. pushl %ebp
    36. .cfi_def_cfa_offset 8
    37. .cfi_offset 5, -8
    38. movl %esp, %ebp
    39. .cfi_def_cfa_register 5
    40. andl $-16, %esp
    41. call ___main
    42. call __Z2fbv
    43. call __Z2fcv
    44. movl $0, %eax
    45. leave
    46. .cfi_restore 5
    47. .cfi_def_cfa 4, 4
    48. ret
    49. .cfi_endproc
    50. LFE935:
    51. .ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
    52. .def __Z2fbv; .scl 2; .type 32; .endef
    53. .def __Z2fcv; .scl 2; .type 32; .endef

    3:如何解决链接的时候重复包含问题

    1. 我们知道c/c++编译的基本单元是.c或.cpp文件,各个基本单元的编译是相互独立的,#ifndef等条件编译只能保证在一个基本单元(单独的.c或.cpp文件)中头文件不会被重复编译,
    2. 但是无法保证两个或者更多基本单元中相同的头文件不会被重复编译
    1. a.h
    2. #pragma once
    3. int A = 2;
    4. ==================================================================================
    5. b.h
    6. #pragma once
    7. #include
    8. #include"a.h"
    9. void fb();
    10. b.cpp
    11. #include"b.h"
    12. void fb() {
    13. printf("%d", A+1);
    14. }
    15. // 预编译 b.cpp
    16. gcc -E b.cpp
    17. # 3 "b.h" 2
    18. # 1 "a.h" 1
    19. # 2 "a.h"
    20. int A = 2;
    21. # 4 "b.h" 2
    22. void fb();
    23. # 2 "b.cpp" 2
    24. void fb() {
    25. printf("%d", A+1);
    26. }
    27. // 编译 gcc -C -S b.cpp -o b.txt
    28. .file "b.cpp"
    29. .section .rdata,"dr"
    30. __ZStL19piecewise_construct:
    31. .space 1
    32. .globl _A
    33. .data
    34. .align 4
    35. _A:
    36. .long 2
    37. .section .rdata,"dr"
    38. LC0:
    39. .ascii "%d\0"
    40. .text
    41. .globl __Z2fbv
    42. .def __Z2fbv; .scl 2; .type 32; .endef
    43. __Z2fbv:
    44. LFB935:
    45. .cfi_startproc
    46. pushl %ebp
    47. .cfi_def_cfa_offset 8
    48. .cfi_offset 5, -8
    49. movl %esp, %ebp
    50. .cfi_def_cfa_register 5
    51. subl $24, %esp
    52. movl _A, %eax
    53. addl $1, %eax
    54. movl %eax, 4(%esp)
    55. movl $LC0, (%esp)
    56. call _printf
    57. nop
    58. leave
    59. .cfi_restore 5
    60. .cfi_def_cfa 4, 4
    61. ret
    62. .cfi_endproc
    63. LFE935:
    64. .ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
    65. .def _printf; .scl 2; .type 32; .endef
    66. ====================================================================================
    67. c.h
    68. #pragma once
    69. #include
    70. #include"a.h"
    71. void fc();
    72. c.cpp
    73. #include"c.h"
    74. void fc() {
    75. printf("%d", A + 2);
    76. }
    77. // 预编译 gcc -E c.cpp
    78. # 3 "c.h" 2
    79. # 1 "a.h" 1
    80. # 2 "a.h"
    81. int A = 2;
    82. # 4 "c.h" 2
    83. void fc();
    84. # 2 "c.cpp" 2
    85. void fc() {
    86. printf("%d", A + 2);
    87. }
    88. // 编译 gcc -C -S c.cpp -o c.txt
    89. .file "c.cpp"
    90. .section .rdata,"dr"
    91. __ZStL19piecewise_construct:
    92. .space 1
    93. .globl _A
    94. .data
    95. .align 4
    96. _A:
    97. .long 2
    98. .section .rdata,"dr"
    99. LC0:
    100. .ascii "%d\0"
    101. .text
    102. .globl __Z2fcv
    103. .def __Z2fcv; .scl 2; .type 32; .endef
    104. __Z2fcv:
    105. LFB935:
    106. .cfi_startproc
    107. pushl %ebp
    108. .cfi_def_cfa_offset 8
    109. .cfi_offset 5, -8
    110. movl %esp, %ebp
    111. .cfi_def_cfa_register 5
    112. subl $24, %esp
    113. movl _A, %eax
    114. addl $2, %eax
    115. movl %eax, 4(%esp)
    116. movl $LC0, (%esp)
    117. call _printf
    118. nop
    119. leave
    120. .cfi_restore 5
    121. .cfi_def_cfa 4, 4
    122. ret
    123. .cfi_endproc
    124. LFE935:
    125. .ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
    126. .def _printf; .scl 2; .type 32; .endef
    127. ================================================================================
    128. main.cpp
    129. #include
    130. #include"b.h"
    131. #include"c.h"
    132. #include
    133. int main() {
    134. fb();
    135. fc();
    136. return 0;
    137. }
    138. // 预编译 gcc -E main.cpp
    139. # 2 "main.cpp" 2
    140. # 1 "b.h" 1
    141. # 1 "a.h" 1
    142. # 2 "a.h"
    143. int A = 2;
    144. # 4 "b.h" 2
    145. void fb();
    146. # 3 "main.cpp" 2
    147. # 1 "c.h" 1
    148. void fc();
    149. # 4 "main.cpp" 2
    150. int main() {
    151. fb();
    152. fc();
    153. return 0;
    154. }
    155. // 编译 gcc -C -S main.cpp -o main.txt
    156. .file "main.cpp"
    157. .section .rdata,"dr"
    158. __ZStL19piecewise_construct:
    159. .space 1
    160. .globl _A
    161. .data
    162. .align 4
    163. _A:
    164. .long 2
    165. .def ___main; .scl 2; .type 32; .endef
    166. .text
    167. .globl _main
    168. .def _main; .scl 2; .type 32; .endef
    169. _main:
    170. LFB935:
    171. .cfi_startproc
    172. pushl %ebp
    173. .cfi_def_cfa_offset 8
    174. .cfi_offset 5, -8
    175. movl %esp, %ebp
    176. .cfi_def_cfa_register 5
    177. andl $-16, %esp
    178. call ___main
    179. call __Z2fbv
    180. call __Z2fcv
    181. movl $0, %eax
    182. leave
    183. .cfi_restore 5
    184. .cfi_def_cfa 4, 4
    185. ret
    186. .cfi_endproc
    187. LFE935:
    188. .ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
    189. .def __Z2fbv; .scl 2; .type 32; .endef
    190. .def __Z2fcv; .scl 2; .type 32; .endef
    191. =====================================================================================
    192. 从预编译和编译的过程我们看出,b.cpp / c.cpp / main.cpp 这三个文件的预编译和编译过程均没有问题
    193. 那么我们看一下,这个程序最后一步:链接过程

    从预编译和编译的过程我们看出,b.cpp / c.cpp / main.cpp 这三个文件的预编译和编译过程均没有问题
    那么我们看一下,这个程序最后一步:链接过程

    1: 然后分别编译gcc -c b.cpp -o b.o和gcc -c main.cpp -o main.o

    2:  然后链接 b.o 和main.o 文件  (gcc b.o main.o -o main,)就会出错,为什么了

    1.  gcc -c b.cpp -o b.o  ---->b.cpp文件被编译成b.o文件,在这个过程中,预处理阶段编译器还是会打开a.h文件,定义#pragma once 并将a.h包含进b.cpp中。
    2. gcc -c c.cpp -o c.o---->c.cpp文件被编译成c.o文件,在这个过程中,请注意预处理阶段,编译器依旧打开a.h文件,此时的#pragma once是否已被定义呢?前面提到不相关的.cpp文件之间的编译是相互独立的,自然,b.cpp的编译不会影响c.cpp的编译过程,
    3. 所以c.cpp中的#pragma once不会受前面b.cpp中#pragma once的影响,也就是c.cpp的#pragma once是未定义的!!于是编译器再次干起了相同的活,定义#pragma once,包含将a.h包含进c.cpp中。

    到此,我们有了b.o和c.o,编译main.cpp后有了main.o,再将它们链接起来生成main时出现问题了:

    • 编译器在编译.c或.cpp文件时,有个很重要的步骤,就是给这些文件中含有的已经定义了的变量分配内存空间,
    • 在a.h中A就是已经定义的变量,由于b.cpp和c.cpp独立,所以A相当于定义了两次,分配了两个不同的内存空间。
    • 在main.o链接b.o和c.o的时候,由于main函数调用了fb和fc函数,这两个函数又调用了A这个变量,对于main函数来说,A变量应该是唯一的,应该有唯一的内存空间,但是fb和fc中的A被分配了不同的内存,内存地址也就不同,main函数无法判断那个才是A的地址,产生了二义性,所以程序会出错。

    4: 如何避免重复定义

    讲了这么多,那么到底怎么样才能避免重复定义呢?
    其实避免重复定义关键是要避免重复编译,防止头文件重复包含是有效避免重复编译的方法,但是最好的方法还是记住那句话:头文件尽量只有声明,不要有定义。这么做不仅仅可以减弱文件间的编译依存关系,减少编译带来的时间性能消耗,更重要的是可以防止重复定义现象的发生,防止程序崩溃。
     

  • 相关阅读:
    BF算法详解(JAVA语言实现)
    VB:二分法查找
    综合实验—增强分析和配置中小型企业网络的综合能力
    tf.Variable
    nodejs事件循环机制
    Android Camera性能分析 第23讲 录像Buffer Path实战和Trace分析
    计算机毕业设计Java电子存证系统(源码+系统+mysql数据库+lw文档)
    生命在于折腾——Fishing软件的编写(易语言)
    静电除尘器的工作原理及使用说明
    高效工作文档产出归类
  • 原文地址:https://blog.csdn.net/u013620306/article/details/127844404