如何在C代码中获取变量的类型?

14
有没有办法在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 是否具有整数或浮点类型。从低级视角来看,这两类类型的加法操作完全不同。

如果只有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编写的遗留代码。我可以使用脚本自动更改代码,但我无法将其重写为另一种语言。

谢谢!


2
你觉得使用一些工具/脚本对源代码进行后处理怎么样?例如,使用clang解析代码,查找类型,插入/调整特定于类型的代码,然后编译呢? - Alexey Frunze
谢谢,亚历克斯。听起来它朝着正确的方向发展。 - Amittai Aviram
1
我在某处读到,用户定义的规约将成为3.1或4.0标准的一部分。Hm 3.1说:reduction({operator|intrinsic_procedure_name}:list)……从未尝试过内置过程名。虽然它只部分解决了类型检测的问题。 - Bort
1
nvm. intrinsic_procedure_name 指的是其他东西。 - Bort
6个回答

10

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

可以在这个答案中找到众多类型的很好的起点。

在Ubuntu 17.10、GCC 7.2.0中进行过测试。GCC只在4.9版本中添加了对此的支持。


5
您可以使用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");

尽管两个或多个主类型可能具有相同的大小,但这将导致复杂性。

13
请不要这样做。很多类型大小相同,这会很容易导致难以发现的错误。 - CoffeeTableEspresso
1
这个回答仍然可能是有用的 - 如果变量已知是不同大小类型集合中的一个。 - Andrew Norris

3

在预编译时,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进行内联和其他各种优化,则此运行时评估未必比混淆的宏慢多少,但它要安全得多。

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

3
GCC提供了typeof扩展。它不是标准的,但足够常见(其他几个编译器,如clang/llvm,也具有该扩展)。
您可以考虑使用MELT(一种用于扩展GCC的特定领域语言)来定制GCC以适应您的目的。

1
谢谢,Basile。从我的帖子中可以看出,我知道typeof,但是typeof无法给我所需的内容:它不返回任何东西;您不能将其或其信息传递给函数。你可以这样做:#define add(x,y,z) {
typeof(x) _x = x;
typeof(y) _y = y;
z = _x + _y;
}在我看来,typeof运算符的用处非常有限。
- Amittai Aviram
1
它不是标准的,大多数C编译器都无法运行。根据我的经验,使用GCC扩展来进行生产代码编写是一个非常糟糕的想法。 - Lundin

2
你可以考虑使用插件或MELT扩展来定制GCC以满足你的需求。但是,这需要理解一些复杂的GCC内部表示(Gimple、Tree),这可能需要至少几天的工作时间。
但在C语言中,类型只是编译时的东西,它们并没有实体化。

1
这差不多就是我最终所做的——不是插件,而是在GCC本身中进行一些代码修改。我找出了如何在编译时从Gimple tree_nodes获取类型信息。这确实需要花费相当长的时间去挖掘。谢谢! - Amittai Aviram

0

通常情况下,无法确定给定字节或字节序列中包含的数据类型。例如,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; 
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接