如何判断一个变量是二维数组、指针数组还是字符型双重指针?

4

假设我们有三个变量:

 char a[4][4];
 char *b[4];
 char **c;

假设以上所有变量都已正确赋值,没有代码错误。所有这些变量都可以使用[]运算符打印它们的值,如下所示:
for( i=0; i<4; i++){
    printf( "%s\n", a[i] );
}
for( i=0; i<4; i++){
    printf( "%s\n", b[i] );
}
for( i=0; i<4; i++){
    printf( "%s\n", c[i] );

仅仅从这些打印语句中,无法确定其真实的数据类型。 如何识别变量的数据类型?

我想到的一个方法是打印出每个索引的内存地址。 对于二维数组,内存地址应该彼此等距分隔。但对于数组指针,我认为内存地址的间距不应该均匀。 有更好的方法来确定这些变量的数据类型吗?


3
使用_Generic() - chux - Reinstate Monica
3
a是一个二维数组,b是一个包含4个指向字符的指针的数组,c是一个指向指针的指针。它们都是不同的且互相独立。但是,没有任何“测试”可以区分这三者。你需要跟踪你所拥有的类型,并正确地传递和返回所需的类型。如果使用C11,你可以采纳chux正确的建议,否则就由你自己决定。 - David C. Rankin
@chux-- 如果在_Generic()中传递的表达式中,b衰减为char **,则_Generic()无法区分char *b[4]和char **c之间的区别;或者我漏掉了什么? - ad absurdum
@DavidBowling 下面发布了解决方案。它使用对象地址的类型,避免了转换。 - chux - Reinstate Monica
@n..m. - 是的!绝对没错。但我只是好奇是否有一种方法可以不查看声明就能找到答案。 - Nguai al
显示剩余2条评论
4个回答

4
您如何判断一个变量是二维数组、指针数组还是char类型的双指针?
有一种方法可以区分类型。 请注意,“2D数组”不是一种类型,更像是类型的分类。 “char类型的双指针”可以被认为是类型char **
将对象的地址传递给_Generic()
#define xtype(X) _Generic((&X), \
    char (*)[4][4]: "char [4][4]", \
    char *(*)[4]  : "char *[4]", \
    char ***      : "char **", \
    char (*)[4]   : "char [4]", \
    char **       : "char *", \
    char *        : "char", \
    default       : "?" \
)

int main(void) {
  char a[4][4];
  char *b[4];
  char **c;
  puts(xtype(a));
  puts(xtype(b));
  puts(xtype(c));
  puts(xtype(a[0]));
  puts(xtype(b[0]));
  puts(xtype(c[0]));
  puts(xtype(a[0][0]));
  puts(xtype(b[0][0]));
  puts(xtype(c[0][0]));
}

输出

char [4][4]
char *[4]
char **
char [4]
char *
char *
char
char
char

_Generic() 是 C 语言中一个很有用的扩展,但其中的细节和正确应用仍具有挑战性。我希望上述内容能让楼主至少部分地区分对象。

有趣的是,我能够使用以下更通用的 _Generica,b,c 中实现平等的区分。我对 _Generic 的某些方面持谨慎态度,因为我怀疑这可能涉及到实现定义行为。

#define xtype(X) _Generic((&X), \
    char (*)[][4] : "char [][4]", \
    char *(*)[]   : "char *[]", \
    char ***      : "char **", \
    char (*)[]    : "char []", \
    char **       : "char *", \
    char *        : "char", \
    default       : "?" \
)

有用吗?也许吧。我认为这样做的理由是为了支持函数重载。这很好,但如果是这样的话,那么就选择一个简单明了、不含糊的语言特性吧。因为如果想要在像 C 这样的语言中引入额外的元编程功能,那么在包含之前最好确保所有问题都已经解决了。 </rant> - doynax
@doynax 我对各种编译器的经验是,它们存在一些微妙的实现差异。这并不是因为编译器做错了,而是因为 C 规范缺乏精确性。通常,未来的版本会解决这些微妙的细节问题。在引入之前很难解决所有问题。另一方面,由于其庞大的遗留代码库,C 很少是正在快速发展的语言。 - chux - Reinstate Monica
1
@chux--(拍了一下自己的额头)好主意。也许这应该是被接受的答案。 - ad absurdum
2
@doynax 很不幸,_Generic确实有一些问题...在C11之后,委员会对此进行了澄清,例如是否将数组指针衰减应用于参数,以及是否从rvalue参数中删除限定符。 - M.M

1

是的,你这样做很危险,因为你不应该对分配做出假设。如果指向本地变量的指针数组 - 这完全合法 - 可能具有与int数组相同的布局。

你必须区分两种情况:

A. 动态评估

C运行时非常小。特别是没有运行时类型信息。因此,仅凭语言的能力就不可能在运行时获取所引用对象的类型。

B. 静态评估

但是,你的例子看起来像是想在编译时确定对象的类型。C11支持_Generic来实现这一点。它类似于switch,但不是评估具有值case的普通表达式,而是使用类型。

C11 Draft, 6.5.1.1


0

我认为这是不可靠的。如果你对编译器非常了解,你可能能够推断出

&a[0][0] 距离 &a[3][3] 是 16、16*2 或者 16*4 字节

&b[0][0] 距离 b[0][3] 是 4、4*2 或者 4*4 字节

其他的就留给 c

但是编译器必须知道如何使它们可用,因此你很可能不能在忽略编译时警告的情况下互换使用它们。


-2
通过使用 sizeof(),您可以区分这些变量,因为 sizeof() 会为不同的变量提供不同的大小。希望这能帮到您。

如果“values”数组的大小等于指针数组的大小,则此方法无效,因为值类型具有与指针相同的大小(即在大多数平台上为longwhatevertype *)。 - Amin Negm-Awad

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