【问题标题】:How to get the type of a variable in C code?如何在 C 代码中获取变量的类型?
【发布时间】:2012-02-06 06:13:01
【问题描述】:

有什么方法可以让我在 C 中自动发现变量的类型,或者通过程序本身的某种机制,或者——更有可能——通过使用编译器传递到它在哪里解析变量并为其分配类型?我正在寻找有关此的一般建议。下面是关于我需要什么以及为什么需要的更多背景信息。

我想更改 OpenMP 缩减子句的语义。此时,似乎最简单的方法是简单地将源代码中的子句(通过脚本)替换为对函数的调用,然后我可以定义函数来实现我想要的归约语义。例如,我的脚本会转换这个

#pragma omp parallel for reduction(+:x)

进入这个:

my_reduction(PLUS, &x, sizeof(x));
#pragma omp parallel for

在哪里,早些时候,我有(比方说)

enum reduction_op {PLUS, MINUS, TIMES, AND,
  OR, BIT_AND, BIT_OR, BIT_XOR, /* ... */};

并且my_reduction 有签名

void my_reduction(enum reduction_op op, void * var, size_t size);

除其他外,my_reduction 必须按照程序员最初的意图将加法运算应用于归约变量。但是我的函数不知道如何正确地做到这一点。特别是,虽然它知道操作的种类(PLUS)、原始变量的位置(var)以及变量类型的大小,但它并不知道变量本身的类型。特别是,它不知道var 是整数类型还是浮点类型。从低层次的 POV 来看,这两类类型的加法运算是完全不同的。

如果 GCC 支持的非标准运算符 typeof 能够按照 sizeof 的工作方式工作——返回某种类型的变量——我可以轻松解决这个问题。但 typeof 并不像 sizeof:显然,它只能在左值声明中使用。

现在,编译器显然在生成可执行代码之前确实知道 x 的类型。这让我想知道我是否可以以某种方式利用 GCC 的解析器,只是为了获取 x 的类型并将其传递给我的脚本,然后再次运行 GCC,以编译我更改的源代码。那么声明就很简单了

enum var_type { INT8, UINT8, INT16, UINT16, /* ,..., */ FLOAT, DOUBLE};
void my_reduction(enum reduction_op op, void * var, enum var_type vtype);

并且my_reduction 可以在取消引用和应用运算符之前进行适当的转换。

如您所见,我试图在 C 中创建一种“调度”机制。为什么不直接使用 C++ 重载呢?因为我的项目限制我使用用 C 编写的遗留源代码。我可以使用脚本自动更改代码,但无法将其重写为其他语言。

谢谢!

【问题讨论】:

  • 如何使用一些工具/脚本对源代码进行后处理?例如。用 clang 解析它,找到类型,插入/调整特定类型的代码,编译?
  • 谢谢,亚历克斯。听起来它正朝着正确的方向发展。
  • 我在某处读到用户定义的缩减将成为 3.1 或 4.0 标准的一部分。嗯 3.1 说:reduction({operator|intrinsic_procedure_name}:list) ..从未尝试过内在程序名称。尽管它只部分解决了您的类型检测。
  • 非虚拟机。 intrinsic_procedure_name 指的是别的东西。

标签: c gcc types openmp typeof


【解决方案1】:

C11 _Generic

不是一个直接的解决方案,但如果您耐心地编写所有类型的代码,它确实可以让您达到预期的结果:

#include <assert.h>
#include <string.h>

#define typename(x) _Generic((x), \
    int:     "int", \
    float:   "float", \
    default: "other")

int main(void) {
    int i;
    float f;
    void* v;
    assert(strcmp(typename(i), "int")   == 0);
    assert(strcmp(typename(f), "float") == 0);
    assert(strcmp(typename(v), "other") == 0);
}

编译并运行:

gcc -std=c11 a.c
./a.out

可以在 in this answer 找到一个很好的起点,其中包含大量类型。

在 Ubuntu 17.10、GCC 7.2.0 中测试。 GCC 仅在 4.9 中添加了支持。

【讨论】:

    【解决方案2】:

    C 并没有办法在预编译时执行此操作,除非您编写大量宏。我会推荐宏方法的洪水,它基本上是这样的:

    void int_reduction (enum reduction_op op, void * var, size_t size);
    
    #define reduction(type,op,var,size) type##_reduction(op, var, size)
    
    ...
    reduction(int, PLUS, &x, sizeof(x)); // function call
    

    请注意,这是一种非常糟糕的做法,并且只能在维护编写不佳的遗留代码时作为最后的手段使用,即使这样也是如此。这种方法没有类型安全或其他此类保证。

    更安全的方法是从调用者显式调用int_reduction(),或者调用一个在运行时决定类型的泛型函数:

    void reduction (enum type, enum reduction_op op, void * var, size_t size)
    {
      switch(type)
      {
        case INT_TYPE:
          int_reduction(op, var, size);
          break;
        ...
      }
    } 
    

    如果 int_reduction 被内联并进行了各种其他优化,则此运行时评估不一定​​比混淆宏慢得多,但它更安全。

    【讨论】:

    • 是的,感谢您的想法。但是你如何找出类型呢?这是我最大的挑战。其余的只是语法——当然,我同意您的 cmets 关于最佳语法的看法。例如,在上述情况下,我如何知道给定变量是 INT_TYPE?我正在编写的脚本将“reduction(+:x)”移动到单独的调用“reduction(INT_TYPE, PLUS, &var, sizeof(x));”必须知道 x 具有 INT_TYPE 类型,但是如何?--我没有编写整个解析器来获取 x 的类型?
    • @AmittaiAviram 您如何知道何时将变量声明为int?我不知道您的数据的性质或数据的来源,所以我无法回答这个问题。如果是某种外部原始数据,那么在进行任何计算之前,您自然必须确定数据类型。这是一个算法问题,与 C 编程语法本身无关。
    • @Lundin--不,不。图片更像这样。假设您有一个main 函数,它在顶部附近有声明int x。再往下说 100 行,你有 #pragma omp parallel for reduction (+:x)。那里的xint 类型,因为这就是它在当前范围内的声明方式。程序员知道这一点并且可以自己注释函数调用,但我希望能够自动执行此操作。
    • 源代码已经存在,带有变量声明和 OpenMP 编译指示。我想要做的是自动重构它以将reduction 子句替换为reduction(enum reduction_op, void * var, enum type, size_t size)(或类似的)形式的函数调用。我的问题是reduction 子句的形式本身没有变量类型的任何表示,因为——当然——编译器在到达该子句时已经被假定知道类型。
    • @AmittaiAviram 您必须将原始数据类型声明为结构,将枚举类型作为第一个成员,然后将指向该结构的指针传递给函数。或者保留原始变量,但在对函数/宏的调用中包含类型。标准 C 中没有其他替代方案。
    【解决方案3】:

    你可以使用sizeof函数来判断类型,让未知类型的变量为var。 那么

    if(sizeof(var)==sizeof(char))
            printf("char");
        else if(sizeof(var)==sizeof(int))
            printf("int");
        else if(sizeof(var)==sizeof(double))
            printf("double");
    

    当两个或多个主要类型可能具有相同的大小时,它会导致复杂化。

    【讨论】:

    • 请不要这样做。许多类型具有相同的大小,这将很容易导致难以发现的错误。
    【解决方案4】:

    GCC 提供typeof 扩展。它不是标准的,但足够普遍(其他几个编译器,例如 clang/llvm,都有它)。

    您或许可以考虑通过使用MELT(一种用于扩展 GCC 的领域特定语言)扩展它来自定义 GCC 以满足您的目的。

    【讨论】:

    • 谢谢,巴西尔。正如您从我的帖子中看到的那样,我知道 typeof,但 typeof 不会给我我需要的东西:它不返回任何东西;您不能将它或其信息传递给函数。你可以这样做:#define add(x,y,z) { \ typeof(x) _x = x; \ typeof(y) _y = y; \z = _x + _y; \ } 在我看来 typeof 运算符的用处非常有限。
    • 它不是标准的,并且不适用于大多数 C 编译器。根据我的经验,将 GCC 扩展用于生产代码是一个非常糟糕的主意。
    【解决方案5】:

    您还可以考虑使用插件或MELT 扩展来自定义 GCC,以满足您的需求。但是,这需要了解一些复杂的 GCC 内部表示(Gimple、Tree)(因此至少要花几天的时间)。

    但是类型在 C 中是只能编译的。它们没有被具体化。

    【讨论】:

    • 这或多或少是我最终要做的——不是插件,而是 GCC 本身内部的一些代码黑客。我想出了如何在编译时从 Gimple tree_nodes 获取类型信息。它确实需要相当多的挖掘。谢谢!
    【解决方案6】:

    一般来说,不可能识别给定字节或字节序列中的数据类型。例如,0 字节可以是空字符串或整数 0。99 的位模式可以是该数字或字母“c”。

    以下是将任意字节序列转换为可打印值的一些技巧。它适用于大多数情况(但不适用于也可能是字符的数字)。它适用于 Windows 7 下的 lcc 编译器,具有 32 位整数、长整数和 64 位双精度数。

    char* OclAnyToString(void* x)
    { char* ss = (char*) x;
      int ind = 0;
    
      int* ix = (int*) x;
      long* lx = (long*) x; 
      double* dx = (double*) x; 
    
      char* sbufi = (char*) calloc(21, sizeof(char)); 
      char* sbufl = (char*) calloc(21, sizeof(char)); 
      char* sbufd = (char*) calloc(21, sizeof(char)); 
    
      if (ss[0] == '\0')
      { sprintf(sbufi, "%d", *ix);
        sprintf(sbufd, "%f", *dx);
        if (strcmp(sbufi,"0") == 0 && 
            strcmp(sbufd,"0.000000") == 0)
        { return "0"; }
        else if (strcmp(sbufd,"0.000000") != 0)
        { return sbufd; }
        else 
        { return sbufi; }
      }
    
      while (isprint(ss[ind]) && 0 < ss[ind] && ss[ind] < 128 && ind < 1024)
      { /* printf("%d\n", ss[ind]); */   
        ind++; 
      }
    
      if (ss[ind] == '\0')
      { return (char*) x; } 
    
      sprintf(sbufi, "%d", *ix);
      sprintf(sbufl, "%ld", *lx);
      sprintf(sbufd, "%f", *dx);
    
      if (strcmp(sbufd,"0.000000") != 0)
      { free(sbufi); 
        free(sbufl); 
      
        return sbufd;
      } 
    
      if (strcmp(sbufi,sbufl) == 0)
      { free(sbufd); 
        free(sbufl); 
        return sbufi; 
      }
      else 
      { free(sbufd); 
        free(sbufi); 
        return sbufl; 
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2021-01-29
      • 2021-02-13
      • 2012-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-09
      相关资源
      最近更新 更多