C 不同文件中定义的同一个全局变量 [英] C the same global variable defined in different files

查看:31
本文介绍了C 不同文件中定义的同一个全局变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在阅读这里的代码(中文).有一段代码是关于在 C 中测试全局变量的.变量 a 已在文件 t.h 中定义,该文件已包含两次.在文件 foo.c 中定义了一个带有一些值的 struct b 和一个 main 函数.在 main.c 文件中,定义了两个未初始化的变量.

I am reading this code from here(in Chinese). There is one piece of code about testing global variable in C. The variable a has been defined in the file t.h which has been included twice. In file foo.c defined a struct b with some value and a main function. In main.c file, defined two variables without initialized.

/* t.h */
#ifndef _H_
#define _H_
int a;
#endif

/* foo.c */
#include <stdio.h>
#include "t.h"

struct {
   char a;
   int b;
} b = { 2, 4 };

int main();

void foo()
{
    printf("foo:	(&a)=0x%08x
	(&b)=0x%08x

        	sizeof(b)=%d
	b.a=%d
	b.b=%d
	main:0x%08x
",
        &a, &b, sizeof b, b.a, b.b, main);
}

/* main.c */
#include <stdio.h>
#include "t.h"

int b;
int c;

int main()
{
    foo();
    printf("main:	(&a)=0x%08x
	(&b)=0x%08x

        	(&c)=0x%08x
	size(b)=%d
	b=%d
	c=%d
",
        &a, &b, &c, sizeof b, b, c);
    return 0;
}

使用Ubuntu GCC 4.4.3编译后,结果如下:

After using Ubuntu GCC 4.4.3 compiling, the result is like this below:

foo:    (&a)=0x0804a024
    (&b)=0x0804a014
    sizeof(b)=8
    b.a=2
    b.b=4
    main:0x080483e4
main:   (&a)=0x0804a024
    (&b)=0x0804a014
    (&c)=0x0804a028
    size(b)=4
    b=2
    c=0

变量ab在两个函数中地址相同,但是b的大小发生了变化.我无法理解它是如何工作的!

Variable a and b has the same address in two function, but the size of b has changed. I can't understand how it worked!

推荐答案

你违反了C的一个定义规则",结果是未定义的行为.单一定义规则"并未在标准中正式声明.我们正在查看不同源文件(又名翻译单元)中的对象,因此我们关注外部定义".阐明了一个外部定义"语义(C11 6.9 p5):

You are violating C's "one definition rule", and the result is undefined behavior. The "one definition rule" is not formally stated in the standard as such. We are looking at objects in different source files (aka, translation units), so we concerned with "external definitions". The "one external definition" semantic is spelled out (C11 6.9 p5):

外部定义是一个外部声明,它也是函数(内联定义除外)或对象的定义.如果用外部链接声明的标识符在表达式中使用(不是作为 sizeof_Alignof 运算符的操作数的一部分,其结果是整数常量),在某处整个程序标识符应该只有一个外部定义;否则,不得超过一个.

An external definition is an external declaration that is also a definition of a function (other than an inline definition) or an object. If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.

这基本上意味着您最多只能定义一个对象一次.(如果外部对象从未在程序中的任何地方使用,则 else 子句允许您根本不定义外部对象.)

Which basically means you are only allowed to define an object at most once. (The otherwise clause allows you to not define an external object at all if it is never used anywhere in the program.)

请注意,b 有两个外部定义.一个是你在foo.c中初始化的结构,另一个是main.c中的暂定定义,(C11 6.9.2p1-2):

Note that you have two external definitions for b. One is the structure that you initialize in foo.c, and the other is the tentative definition in main.c, (C11 6.9.2 p1-2):

如果对象标识符的声明具有文件范围和初始化程序,则声明是标识符的外部定义.

If the declaration of an identifier for an object has file scope and an initializer, the declaration is an external definition for the identifier.

具有文件范围的对象的标识符声明没有初始化程序,没有存储类说明符或具有存储类说明符static,构成一个暂定定义.如果翻译单元包含一个或多个标识符的暂定定义,并且翻译单元不包含该标识符的外部定义,则行为与翻译单元包含该标识符的文件范围声明完全相同,复合类型为翻译单元的末尾,初始化器等于 0.

A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier static, constitutes a tentative definition. If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.

所以你有多个b的定义.但是,还有另一个错误,因为您定义了具有不同类型的 b.首先请注意,允许对具有外部链接的同一对象进行多个声明.但是,当在两个不同的源文件中使用相同的名称时,该名称指的是同一个对象(C11 6.2.2 p2):

So you have multiple definitions of b. However, there is another error, in that you have defined b with different types. First note that multiple declarations to the same object with external linkage is allowed. However, when the same name is used in two different source files, that name refers to the same object (C11 6.2.2 p2):

在构成整个程序的一组翻译单元和库中,每个具有外部链接的特定标识符的声明表示相同的对象或功能.

In the set of translation units and libraries that constitutes an entire program, each declaration of a particular identifier with external linkage denotes the same object or function.

C 严格限制对同一对象的声明 (C11 6.2.7 p2):

C puts a strict limitation on declarations to the same object (C11 6.2.7 p2):

引用同一对象或函数的所有声明都应具有兼容的类型;否则,行为未定义.

All declarations that refer to the same object or function shall have compatible type; otherwise, the behavior is undefined.

由于每个源文件中 b 的类型实际上并不匹配,因此行为未定义.(什么构成兼容类型在所有 C11 6.2.7 中都有详细描述,但基本上归结为类型必须匹配.)

Since the types for b in each of your source files do not actually match, the behavior is undefined. (What constitutes a compatible type is described in detail in all of C11 6.2.7, but it basically boils down to being that the types have to match.)

所以你对 b 有两个失败:

So you have two failings for b:

  • 多个定义.
  • 具有不兼容类型的多个声明.

从技术上讲,您在两个源文件中声明 int a 也违反了单一定义规则".请注意,a 具有外部链接 (C11 6.2.2 p5):

Technically, your declaration of int a in both of your source files also violates the "one definition rule". Note that a has external linkage (C11 6.2.2 p5):

如果对象标识符的声明具有文件范围且没有存储类说明符,则其链接是外部的.

If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.

但是,从前面的 C11 6.9.2 的引用来看,那些 int a 暂定定义是外部定义,您只能使用顶部 C11 6.9 的引用中的一个.

But, from the quote from C11 6.9.2 earlier, those int a tentative definitions are external definitions, and you are only allowed one of those from the quote from C11 6.9 at the top.

通常的免责声明适用于未定义的行为.任何事情都可能发生,这将包括您观察到的行为.

The usual disclaimers apply for undefined behavior. Anything can happen, and that would include the behavior you observed.

C 的一个常见扩展是允许多个外部定义,并在信息性附录 J.5 (C11 J.5.11) 中的 C 标准中进行了描述:

A common extension to C is to allow multiple external definitions, and is described in the C standard in the informative Annex J.5 (C11 J.5.11):

一个对象的标识符可能有多个外部定义,带有或没有显式使用关键字extern如果定义不一致,或不止一个被初始化,行为未定义 (6.9.2).

There may be more than one external definition for the identifier of an object, with or without the explicit use of the keyword extern; if the definitions disagree, or more than one is initialized, the behavior is undefined (6.9.2).

(重点是我的.)由于 a 的定义是一致的,因此没有什么坏处,但 b 的定义却不一致.这个扩展解释了为什么你的编译器不会抱怨存在多个定义.根据 C11 6.2.2 的引用,链接器将尝试协调对同一对象的多个引用.

(Emphasis is mine.) Since the definitions for a agree, there is no harm there, but the definitions for b do not agree. This extension explains why your compiler does not complain about the presence of multiple definitions. From the quote of C11 6.2.2, the linker will attempt to reconcile the multiple references to the same object.

链接器通常使用两种模型之一来协调多个翻译单元中同一符号的多个定义.这些是通用模型"和参考/定义模型".在Common Model"中,多个同名对象以union风格的方式折叠成一个对象,使对象呈现最大定义的大小.在Ref/Def 模型"中,每个外部名称必须只有一个定义.

Linkers typically use one of two models for reconciling multiple definitions of the same symbol in multiple translation units. These are the "Common Model" and the "Ref/Def Model". In the "Common Model", multiple objects with the same name are folded into a single object in a union style manner so that the object takes on the size of the largest definition. In the "Ref/Def Model", each external name must have exactly one definition.

GNU 工具链默认使用通用模型"和宽松的 Ref/Def 模型",它对单个翻译单元执行严格的定义规则,但不会抱怨跨多个翻译单元的违规行为.

The GNU toolchain uses the "Common Model" by default, and a "Relaxed Ref/Def Model", where it enforces a strictly one definition rule for a single translation unit, but does not complain about violations across multiple translation units.

可以使用 -fno-common 选项在 GNU 编译器中抑制通用模型".当我在我的系统上对此进行测试时,它会导致类似于您的代码的严格 Ref/Def 模型"行为:

The "Common Model" can be suppressed in the GNU compiler by using the -fno-common option. When I tested this on my system, it caused "Strict Ref/Def Model" behavior for code similar to yours:

$ cat a.c
#include <stdio.h>
int a;
struct { char a; int b; } b = { 2, 4 };
void foo () { printf("%zu
", sizeof(b)); }
$ cat b.c
#include <stdio.h>
extern void foo();
int a, b;
int main () { printf("%zu
", sizeof(b)); foo(); }
$ gcc -fno-common a.c b.c
/tmp/ccd4fSOL.o:(.bss+0x0): multiple definition of `a'
/tmp/ccMoQ72v.o:(.bss+0x0): first defined here
/tmp/ccd4fSOL.o:(.bss+0x4): multiple definition of `b'
/tmp/ccMoQ72v.o:(.data+0x0): first defined here
/usr/bin/ld: Warning: size of symbol `b' changed from 8 in /tmp/ccMoQ72v.o to 4 in /tmp/ccd4fSOL.o
collect2: ld returned 1 exit status
$

我个人认为无论多对象定义的解析模型如何,都应该始终提供链接器发出的最后一个警告,但这既不是这里也不是那里.

参考资料:
很遗憾,我不能给你我的 C11 标准副本的链接
什么是 C 中的 extern 变量?
链接器初学者指南"
关于外部变量模型的 SAS 文档

这篇关于C 不同文件中定义的同一个全局变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆