这可能是一个愚蠢的问题,但也许有人可以提供一些见解。
我在头文件中定义了一些全局变量(是的,我知道这很糟糕,但这只是一种假设情况)。我将此头文件包含在两个源文件中,然后将这些文件编译为两个目标文件。全局符号不会在代码中的任何位置引用。
如果源文件是C,那么看起来编译器省略了全局符号,并且所有链接都没有错误。如果源文件是C ++,则符号包含在两个目标文件中,然后我会收到链接器错误。对于C ++,当我包含标题时,我使用extern“C”。
我正在使用VS2005的Microsoft编译器。
这是我的代码:
头文件(test.h):
#ifndef __TEST_H
#define __TEST_H
/* declaration in header file */
void *ptr;
#endif
C源文件:
test1.c
#include "test.h"
int main( ) {
return 0;
}
test2.c中
#include "test.h"
C ++源文件:
test1.cpp
extern "C" {
#include "test.h"
}
int main( ) {
return 0;
}
测试2.cpp
extern "C" {
#include "test.h"
}
对于C,目标文件看起来像这样:
Dump of file test1.obj
File Type: COFF OBJECT
COFF SYMBOL TABLE
000 006DC627 ABS notype Static | @comp.id
001 00000001 ABS notype Static | @feat.00
002 00000000 SECT1 notype Static | .drectve
Section length 2F, #relocs 0, #linenums 0, checksum 0
004 00000000 SECT2 notype Static | .debug$S
Section length 228, #relocs 7, #linenums 0, checksum 0
006 00000004 UNDEF notype External | _ptr
007 00000000 SECT3 notype Static | .text
Section length 7, #relocs 0, #linenums 0, checksum 96F779C9
009 00000000 SECT3 notype () External | _main
00A 00000000 SECT4 notype Static | .debug$T
Section length 1C, #relocs 0, #linenums 0, checksum 0
String Table Size = 0x0 bytes
对于C ++,他们看起来像这样:
Dump of file test1.obj
File Type: COFF OBJECT
COFF SYMBOL TABLE
000 006EC627 ABS notype Static | @comp.id
001 00000001 ABS notype Static | @feat.00
002 00000000 SECT1 notype Static | .drectve
Section length 2F, #relocs 0, #linenums 0, checksum 0
004 00000000 SECT2 notype Static | .debug$S
Section length 228, #relocs 7, #linenums 0, checksum 0
006 00000000 SECT3 notype Static | .bss
Section length 4, #relocs 0, #linenums 0, checksum 0
008 00000000 SECT3 notype External | _ptr
009 00000000 SECT4 notype Static | .text
Section length 7, #relocs 0, #linenums 0, checksum 96F779C9
00B 00000000 SECT4 notype () External | _main
00C 00000000 SECT5 notype Static | .debug$T
Section length 1C, #relocs 0, #linenums 0, checksum 0
String Table Size = 0x0 bytes
我注意到_ptr在编译C源时被列为UNDEF,并且在编译C ++源时定义它,这会导致链接器错误。
我明白这在现实生活中不是一件好事,我只是想了解为什么这是不同的。
感谢。
答案 0 :(得分:12)
在C中,标识符有三种不同类型的“链接”:
static
关键字声明的对象。对于具有外部链接的对象,您只能有一个定义。由于您的头文件定义了这样一个对象并包含在两个C文件中,因此它是未定义的行为(但见下文)。您的C编译器没有抱怨的事实并不意味着在C中这样做是可以的。为此,您必须阅读C标准。 (或者,假设您的编译器中没有错误,如果它是在符合标准的模式下调用,并且如果它抱怨[给出诊断],则可能意味着您的程序不符合。)
换句话说,您无法通过测试某些内容并检查编译器是否允许该内容来测试该语言允许的内容。为此,您必须阅读标准。
请注意,定义与暂定定义之间存在细微差别。
$ cat a.c
int x = 0;
$ cat b.c
#include <stdio.h>
int x = 0;
int main(void)
{
printf("%d\n", x);
return 0;
}
$ gcc -ansi -pedantic -W -Wall -c a.c
$ gcc -ansi -pedantic -W -Wall -c b.c
$ gcc -o def a.o b.o
b.o:(.bss+0x0): multiple definition of `x'
a.o:(.bss+0x0): first defined here
collect2: ld returned 1 exit status
现在,让我们改变a.c
:
$ cat a.c
int x; /* Note missing " = 0", so tentative definition */
现在编译它:
$ gcc -ansi -pedantic -W -Wall -c a.c
$ gcc -o def a.o b.o
$ ./def
0
我们可以改为b.c
:
$ cat a.c
int x = 0;
$ cat b.c
#include <stdio.h>
int x; /* tentative definition */
int main(void)
{
printf("%d\n", x);
return 0;
}
$ gcc -ansi -pedantic -W -Wall -c a.c
$ gcc -ansi -pedantic -W -Wall -c b.c
$ gcc -o def a.o b.o
$ ./def
0
如果没有其他定义,“暂定”将成为C中的“真实定义”。因此,我们可以将两个文件都更改为包含int x;
,这将是合法的C.
因此,您可能在头文件中有一个暂定的定义。我们需要确定实际的代码。
C标准说以下是未定义的行为(附录J.2p1):
使用具有外部链接的标识符,但在程序中不存在 只有一个标识符的外部定义,或者没有使用标识符 存在标识符的多个外部定义。
C ++可能有不同的规则。
编辑:根据this thread on comp.lang.c++
,C ++没有暂定定义。原因是:
这避免了内置类型和用户定义类型的不同初始化规则。
(该主题处理相同的问题,顺便说一句。)
现在我几乎可以肯定OP的代码包含C在头文件中调用“暂定定义”的内容,这使得它在C语言中合法并且在C ++中是非法的。只有当我们看到代码时,我们才会确定。
有关“暂定定义”的更多信息以及需要它们的原因在于excellent post on comp.lang.c(Chris Torek)。
答案 1 :(得分:1)
不要在标题中定义变量 - 这是邪恶的。
仅在标题中声明变量 - 使用明确的extern
关键字。
不同之处在于C ++明确要求一个定义规则 - 在C ++程序中可能只有一个具有外部链接的给定变量的定义。
严格来说,C标准具有相同的要求。但是,该标准的附录J列出了允许多个未初始化定义被视为一个的共同扩展 - 它被称为“通用定义”,因为它类似于(旧学校)Fortran中的COMMON块的行为(Fortran IV,又名例如Fortran 66和Fortran 77。
警告:是的,如果你知道不需要提出这样的问题,偶尔也很少偶尔会在标题中定义变量。但是这样的场合是如此之少,以至于它足够准确地说“不要在标题中定义变量”。
Christoph提出了一个关于'static const
变量'的有趣观点。围绕这个问题的一种狡猾的措辞是声称“常数不是变量”。然而,Christoph是对的:特别是在C ++中,使用了静态const'变量'。在C中,我认为这些常量会倾向于从编译器中引出“未使用”的警告;但是,GCC 4.4.2没有
给出这个最小代码时给出任何警告:
static const int x = 3;
extern int p(void);
int p(void)
{
return(3);
}
即使在“x
”下,它也不会抱怨未使用的-Wall -Wextra
。因此,可以在标题中定义常量 - 就像在'static const SomeType constName = InitialValue;
'中一样。至少,如果您的编译器是GCC,尽管代码也会在Sun Studio编译器下使用“cc -v
”进行编译而不会发出警告:
C compiler: /compilers/v12/SUNWspro/bin/cc
cc: Sun C 5.9 SunOS_sparc Patch 124867-09 2008/11/25
acomp: Sun C 5.9 SunOS_sparc Patch 124867-09 2008/11/25
iropt: Sun Compiler Common 12 SunOS_sparc Patch 124861-13 2009/03/10
cg: Sun Compiler Common 12 SunOS_sparc Patch 124861-13 2009/03/10
ld: Software Generation Utilities - Solaris Link Editors: 5.10-1.486