【问题标题】:C typedef in header file variably modified at file scope头文件中的 C typedef 在文件范围内可变地修改
【发布时间】:2011-12-26 16:04:52
【问题描述】:

我需要在两个源文件中包含一些 typedef 定义:

typedef double mat[MATSIZE][MATSIZE] ;

所以我创建了 def.h,其中包括:

#ifndef DEF_H
#define DEF_H
typedef double mat[MATSIZE][MATSIZE] ;
#endif

在两个 .c 文件中,我通过以下方式包含它:

在第一个处理的文件中:

#define MATSIZE 4
#include "def.h"

在第二个 .c 文件中:

extern int MATSIZE;
#include "def.h"

但我明白了

error: variably modified ‘mat’ at file scope

我做错了什么?

【问题讨论】:

  • 您还必须定义MATSIZE。见stackoverflow.com/questions/1712592/…
  • 我有 '#define MATSIZE 4 #define MATLENGTH MATSIZE*MATSIZE #include "def.h" '在首先处理的这些 .c 文件中,以及 'extern int MATSIZE; #include "def.h" '在第二个
  • 一个 .c 文件说 MATSIZE 是一个常数。另一个说它是一个(外部)int。其中一个在撒谎……
  • 没有。在 file1 中,它是一个 preprocessor 定义。它指示预处理器在每次发生时用字符串 '4' 替换字符串 'MATSIZE'。 (因此编译器在扫描.h 文件时将有效地看到typedef double mat[4][4];)。第二个 .c 文件用 name MATSIZE(但未知值)声明了一个(外部)int,因此编译器在扫描 .h 文件时将有效地看到 typedef double mat[MATSIZE][MATZIZE];,并认为 MATSIZE 是具有未知值的(外部)int;这是不可接受的。
  • 是的,或多或少。您想要的是保证 'mat' 的 typedef 每次被另一个 (.c) 文件包含时都产生完全相同的类型。最简单的方法是将#define MATSIZE xxx 保留在同一个(.h 文件)中。

标签: c header typedef


【解决方案1】:

可变长度数组 (VLA) 的概念是 C99 的“新”概念。

在 C99 之前,您只能使用实常数来指定数组的大小。以下代码在块范围或文件范围内都是非法的。

const int size = 42; /* size is not a real constant */
int boo[size];

C99 引入了 VLA 用于块范围。上面的示例代码是合法的 C99,只要它发生在块范围内。您的定义在文件范围内,因此无效。


另外,让相同的 typedef 引用两种不同的类型是一个非常糟糕的想法。

【讨论】:

  • C 中没有“可变修改数组”。不过,有可变长度数组。错误消息似乎没有说明后者。
  • 哎呀...被原始消息冲昏了头脑。我的意思是可变长度数组。帖子已编辑。谢谢@Jens
【解决方案2】:

当数组定义在块之外(在文件范围或全局范围内)时,必须在编译时知道其大小。这意味着数组上的每个维度都必须是一个常量整数值(或者,对于第一个维度,它可以由数组的初始化程序隐含)。

如果您使用 C89 编译器,您可能会收到有关非常量数组维度的消息。 GCC 4.6.1 给出了“在文件范围内可变修改 mat”消息。

C99 将可变长度数组添加到曲目中,但它们只能出现在块或参数列表中,其大小可以在运行时确定。

所以,在一个函数中,你可以合法地写:

extern int MATSIZE;

extern void func(void);

void func(void)
{
    typedef double mat[MATSIZE][MATSIZE];
    // ...
}

(需要函数声明来避免以下警告:

warning: no previous prototype for ‘func’ [-Wmissing-prototypes]

因为我习惯用-Wmissing-prototypes编译。)

另一个问题是,在一个文件中,MATSIZE 是编译时 (#defined) 常量;另一方面,显然有一个整数变量MATSIZE。这些是完全不相关的。因此类型不同。


typdef 是块作用域

wildplasser 关心typedef 是块作用域还是全局作用域。它是块作用域的,正如这段原本可以执行的代码所展示的那样:

#include <stdio.h>

static void function(void)
{
    typedef int i;
    i j = 1;
    printf("j = %d\n", j);
    {
    typedef double i;
    i j = 2.1;
    printf("j = %f\n", j);
    }
    {
    typedef char i[12];
    i j = "this works";
    printf("j = %s\n", j);
    }
}

int main(void)
{
    function();
    return(0);
}

如果提交给我进行代码审查,它将被立即拒绝。但是,这充分说明了一点。

【讨论】:

  • 恕我直言,typedef 具有文件范围,即使从函数内部发出也是如此。你是想迷惑那个可怜的男孩吗?
  • IMO(并且在 C 标准看来),您是不正确的; typedef 是块作用域。例如,C99 §6.7.7 类型定义在 ¶2 中说 如果 typedef 名称指定了可变修改的类型,则它应具有块范围
  • 在那种情况下,你甚至连我都搞糊涂了;-] 从来没有用过。无论如何,它看起来像一个帕斯卡主义......啊,我明白了:只适用于 VLA。这是有道理的。
  • 任何typedef 都可以是块作用域;可变修改的 typedef 只能是块范围(不能是文件范围)。
  • 好吧:您的 code review 部分非常有意义:它唯一的有效用途与 VLA 相关。除了 VLA,我想不出任何 sane 使用块范围的 typedef。 (例如在 c89 / c90 代码中)
【解决方案3】:

MATSIZE 未知。这就是您遇到此问题的原因。

#ifndef DEF_H
#define DEF_H

#define MATSIZE 100 /* or whatever */

typedef double mat[MATSIZE][MATSIZE]

#endif 

【讨论】:

  • 为什么将包含保护宏形式DEF_H改为_DEF_H?原件是完美的,而您的则调用未定义的行为。基本上,以下划线开头的名称都在实现名称空间中,您不应该在应用程序中使用它们。
  • @jens heh 习惯的力量...你是对的,我会更新修复。
  • @querty 把 MATSIZE 放到 def.h 中!
猜你喜欢
  • 2012-11-18
  • 2014-03-07
  • 2019-11-16
  • 2010-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多