在C中使用.h文件的异常情况

art*_*sin 76 c c++

在阅读有关过滤的文章时,我发现了一些奇怪的.h文件使用 - 用它来填充系数数组:

#define N 100 // filter order
float h[N] = { #include "f1.h" }; //insert coefficients of filter
float x[N];
float y[N];

short my_FIR(short sample_data)
{
  float result = 0;

  for ( int i = N - 2 ; i >= 0 ; i-- )
  {
    x[i + 1] = x[i];
    y[i + 1] = y[i];
  }

  x[0] = (float)sample_data;

  for (int k = 0; k < N; k++)
  {
    result = result + x[k]*h[k];
  }
  y[0] = result;

  return ((short)result);
}
Run Code Online (Sandbox Code Playgroud)

那么,使用float h[N] = { #include "f1.h" };这种方式是正常的做法吗?

Bas*_*tch 130

预处理指令就像#include只是在做一些文字替换(见GNU的文档CPPGCC).它可以出现在任何地方(注释和字符串文字之外).

但是,a #include应该将其#作为其行的第一个非空白字符.所以你要编码

float h[N] = {
  #include "f1.h"
};
Run Code Online (Sandbox Code Playgroud)

最初的问题没有#include自己的行,所以错误的代码.

这不是正常的做法,但允许练习.在这种情况下,我建议使用一些其他扩展,.h例如使用#include "f1.def"#include "f1.data"...

请您的编译器向您显示预处理的表单.使用GCC编译gcc -C -E -Wall yoursource.c > yoursource.i并使用编辑器或寻呼机查看生成的内容yoursource.i

我实际上更喜欢在自己的源文件中包含这些数据.所以我建议h-data.c使用像GNU awk这样的工具生成一个自包含的文件(所以文件h-data.c将以...开头const float h[345] = {并以};... 结束)如果它是一个常量数据,最好声明它const float h[](所以它可以坐在读取- 仅.rodata在Linux上的细分市场).此外,如果嵌入数据很大,编译器可能需要时间(无用地)优化它(然后您可以h-data.c快速编译而无需优化).

  • 因为`>`比`-o`短一个字符,因为我很蹩脚. (25认同)
  • 实际上,我的第一个Unix是Sun3,SunOS3.2 ......(所以1987年) (10认同)
  • 为什么重定向输出而不只是使用gcc -C -E -o yoursource.i -Wall yoursource.c?有选择是有原因的! (3认同)

utn*_*tim 10

那么,通常的做法是使用float h [N] = {#include"f1.h"}; 这条路?

这是不正常的,但它是有效的(将被编译器接受).

使用它的优点:它可以为您提供考虑更好解决方案所需的少量工作.

缺点:

  • 它会增加代码的WTF/SLOC比率.
  • 它在客户端代码和包含的代码中引入了不寻常的语法.
  • 为了理解f1.h的作用,你必须看看它是如何使用的(这意味着你需要在你的项目中添加额外的文档来解释这个野兽,否则人们将不得不阅读代码来看看它是什么意味着 - 两种方案都不可接受)

这是在编写代码之前花费额外20分钟思考的情况之一,可以在项目的生命周期中为您节省几十个小时的诅咒代码和开发人员.

  • 缺少换行符,`#include`应该在一个单独的行上. (4认同)

bar*_*nos 10

正如之前的答案中已经解释的那样,这不是正常的做法,但它是有效的.

这是一个替代解决方案:

文件f1.h:

#ifndef F1_H
#define F1_H

#define F1_ARRAY                   \
{                                  \
     0, 1, 2, 3, 4, 5, 6, 7, 8, 9, \
    10,11,12,13,14,15,16,17,18,19, \
    20,21,22,23,24,25,26,27,28,29, \
    30,31,32,33,34,35,36,37,38,39, \
    40,41,42,43,44,45,46,47,48,49, \
    50,51,52,53,54,55,56,57,58,59, \
    60,61,62,63,64,65,66,67,68,69, \
    70,71,72,73,74,75,76,77,78,79, \
    80,81,82,83,84,85,86,87,88,89, \
    90,91,92,93,94,95,96,97,98,99  \
}

// Values above used as an example

#endif
Run Code Online (Sandbox Code Playgroud)

文件f1.c:

#include "f1.h"

float h[] = F1_ARRAY;

#define N (sizeof(h)/sizeof(*h))

...
Run Code Online (Sandbox Code Playgroud)

  • 这种方法"烧掉"符号"F1_ARRAY"和"F1_H".当您有生成的文件时,避免使用这些预处理程序符号可能会很有用.对于人工创建的头文件,您的解决方案更好,但不适用于一堆生成的数据文件. (7认同)
  • 我不明白为什么这比现有解决方案更好.现在,自动生成该数据文件更加困难,并且它会污染符号空间.正如harper已经提到的那样,对于人类生成的数据,这可能会更好,但它仍然比让人类在真实的源文件中生成它更糟糕.如果它过于复杂,那么人类的生成可能是错的. (4认同)

Mat*_* M. 9

不,这不是正常的做法.

直接使用这种格式几乎没有优势,而是可以在单独的源文件中生成数据,或者在这种情况下至少可以形成完整的定义.


然而,存在一种"模式",其涉及在这样的随机位置包括文件:X-宏,例如那些.

X-宏的用途是定义一个集合一次,并在不同的地方使用它.单一定义确保整体的一致性.作为一个简单的例子,考虑:

// def.inc
MYPROJECT_DEF_MACRO(Error,   Red,    0xff0000)
MYPROJECT_DEF_MACRO(Warning, Orange, 0xffa500)
MYPROJECT_DEF_MACRO(Correct, Green,  0x7fff00)
Run Code Online (Sandbox Code Playgroud)

现在可以以多种方式使用:

// MessageCategory.hpp
#ifndef MYPROJECT_MESSAGE_CATEGORY_HPP_INCLUDED
#define MYPROJECT_MESSAGE_CATEGORY_HPP_INCLUDED

namespace myproject {

    enum class MessageCategory {
#   define MYPROJECT_DEF_MACRO(Name_, dummy0_, dummy1_) Name_,
#   include "def.inc"
#   undef MYPROJECT_DEF_MACRO
    NumberOfMessageCategories
    }; // enum class MessageCategory

    enum class MessageColor {
#   define MYPROJECT_DEF_MACRO(dumm0_, Color_, dummy1_) Color_,
#   include "def.inc"
#   undef MYPROJECT_DEF_MACRO
    NumberOfMessageColors
    }; // enum class MessageColor

    MessageColor getAssociatedColorName(MessageCategory category);

    RGBColor getAssociatedColorCode(MessageCategory category);

} // namespace myproject

#endif // MYPROJECT_MESSAGE_CATEGORY_HPP_INCLUDED
Run Code Online (Sandbox Code Playgroud)


fja*_*don 7

很久以前人们过度使用预处理器.例如,参见设计的XPM文件格式,以便人们可以:

#include "myimage.xpm"
Run Code Online (Sandbox Code Playgroud)

在他们的C代码中.

它不再被认为是好的.

OP的代码看起来像C我将谈论的那样C

为什么过度使用预处理器?

预处理程序#include指令旨在包含源代码.在这种情况下,在OP的情况下,它不是真正的源代码而是数据.

为什么它被认为是坏的?

因为它非常不灵活.如果不重新编译整个应用程序,则无法更改图像.您甚至不能包含两个具有相同名称的图像,因为它将生成不可编译的代码.在OP的情况下,他无法在不重新编译应用程序的情况下更改数据.

另一个问题是它在数据和源代码之间创建紧密耦合,例如,数据文件必须至少包含N源代码文件中定义的宏指定的值的数量.

紧耦合也会对数据施加格式,例如,如果要存储10x10矩阵值,则可以选择在源代码中使用单维数组或二维数组.从一种格式切换到另一种格式会对数据文件进行更改.

的此问题加载数据容易地解决通过使用标准的I/O功能.如果您确实需要包含一些默认图像,则可以为源代码中的图像指定默认路径.这至少允许用户更改此值(通过编译时的选项#define-D选项),或更新图像文件而无需重新编译.

在OP的情况下,如果FIR系数和x, y向量作为参数传递,其代码将更加可重用.您可以创建一个struct以保持这些值.代码效率不高,即使使用其他系数也可以重复使用.除非用户传递覆盖文件路径的命令行参数,否则可以在启动时从默认文件加载系数.这将消除对任何全局变量的需要,并使程序员的意图明确.你甚至可以在两个线程中使用相同的FIR函数,前提是每个线程都有自己的struct.

什么时候可以接受?

当你无法动态加载数据时.在这种情况下,您必须静态加载数据,并且您不得不使用此类技术.

我们应该注意,无法访问文件意味着您正在为非常有限的平台编程,因此您必须进行权衡.例如,如果您的代码在微控制器上运行,则会出现这种情况.

但即使在这种情况下,我更愿意创建一个真正的C源文件,而不是从半格式化文件中包含浮点值.

例如,提供C返回系数的实函数,而不是具有半格式化的数据文件.C然后可以在两个不同的文件中定义此函数,一个用于开发目的的I/O,另一个用于发布版本的返回静态数据.你可以编译正确的源文件conditionnaly.


sup*_*cat 5

有时需要使用外部工具生成基于包含源代码的其他文件的.C文件,外部工具生成C文件,其中包含大量代码硬连接到生成工具中,或者让代码使用#include指令以各种"不寻常"的方式.在这些方法中,我认为后者 - 虽然icky - 可能往往是最不邪恶的.

我建议避免使用.h不符合与头文件相关的常规约定的文件的后缀(例如,通过包含方法定义,分配空间,需要不寻常的包含上下文(例如在方法的中间),需要多个包括不同的宏定义等.我通常也避免使用.c.cpp用于通过其他文件合并的文件,#include除非这些文件主要是独立使用的[我可能在某些情况下例如有一个fooDebug.c包含#define SPECIAL_FOO_DEBUG_VERSION[newline]`#include"foo的文件. c"``如果我希望从同一个源生成两个具有不同名称的目标文件,其中一个肯定是"普通"版本.

我通常的做法是使用.i人工生成或机器生成的文件作为后缀,这些文件被设计为包含在内,但通常用于其他C或C++源文件; 如果文件是机器生成的,我通常会将生成工具作为第一行包含标识用于创建它的工具的注释.

顺便说一句,我曾经使用过的一个技巧就是当我想让一个程序只使用一个批处理文件来构建时,没有任何第三方工具,但想要计算它的构建次数.在我的批处理文件中,我包含了echo +1 >> vercount.i; 然后在文件vercount.c中,如果我没记错的话:

const int build_count = 0
#include "vercount.i"
;
Run Code Online (Sandbox Code Playgroud)

实际效果是,我获得的值在每个构建时都会递增,而不必依赖任何第三方工具来生成它.