4

Lets say we have 3 variables:

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

Lets say all of the above variables have correctly assigned values. There is no code errors.

All of these variables can print their values using [] operator like below:

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] );

Just looking at these print statements there is no way to identify its true datatypes. How to identify its variable datatypes?

One idea I had was to print out the memory addresses of each index. With 2-D array, the memory address should be separated with equal distance from each other. But with pointer of array, I expected the memory addresses to be not uniformly spatially distant from each other. Is there a better way to find out datatypes of these variables?

Nguai al
  • 958
  • 5
  • 15
  • 3
  • 3
    `a` is a 2D array, `b` is an *array of pointers to char* `[4]`, `c` is a *pointer-to-pointer-to-char* all are distinct and different as you note. However, there is no *"test"* to determine between the three. You keep track of what you have and you properly pass and return the type needed. With C11, you can take chux correct advice, otherwise, it's up to you. – David C. Rankin Apr 19 '17 at 05:00
  • @chux-- `_Generic()` wouldn't distinguish between `char *b[4]` and `char **c`, since `b` decays to `char **` in the expression passed to `_Generic()`; or am I missing something? – ad absurdum Apr 19 '17 at 05:45
  • @DavidBowling Solution posted below. It uses the type of the address of the object, avoiding the conversion. – chux - Reinstate Monica Apr 19 '17 at 15:37
  • Look at the declaration. The type is there. – n. m. could be an AI Apr 19 '17 at 16:04
  • @n..m. - Yes! Absolutely.. But I was just curious to find out if there is a way without looking at the declaration. – Nguai al Apr 19 '17 at 16:06
  • You may find the ideas on this thread useful: http://stackoverflow.com/questions/16794900/validate-an-argument-is-array-type-in-c-c-macro-on-compile-time – M.M Apr 19 '17 at 23:18

4 Answers4

4

How can you tell whether a variable is a 2D array, array of pointers or double pointers of char?

There is a way to distinguish type.
Note that "2D array" is not a type - more like a classification of types.
"double pointers of char" can be consider to be the type char **

Pass the address of the object to _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]));
}

Output

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

_Generic() is a useful addition to C and some of its details and proper application are still challenging. I hope the above allows at least a partial ability for OP to distinguish objects.

Interestingly, I was able to use a more generic _Generic as below with equal distinguishably amongst a,b,c. I am wary of some aspects of _Generic as I suspect implementation defined behavior.

#define xtype(X) _Generic((&X), \
    char (*)[][4] : "char [][4]", \
    char *(*)[]   : "char *[]", \
    char ***      : "char **", \
    char (*)[]    : "char []", \
    char **       : "char *", \
    char *        : "char", \
    default       : "?" \
)
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Useful? Perhaps. I gather the rationale was to support function overloads. Fair enough, but if so then pick then a straightforward and unambiguous language feature _of that_. Because if the idea was introduce additional meta-programming facilities to a language like C then you had better be sure to at least have all of the kinks worked out prior to inclusion. – doynax Apr 19 '17 at 16:23
  • @doynax My experience with various compilers is that there are some subtle implementation differences. Not so much due to the compiler getting it wrong as much as the C spec lacking precision. Typically a future version will sort of these nuanced details. It is hard to get all the kinks worked out prior to introduction. OTOH, C is rarely the language that is evolving fast given is huge legacy code base. – chux - Reinstate Monica Apr 19 '17 at 16:34
  • 1
    @chux-- (smack self on forehead) nice trick. Perhaps this should be the accepted answer. – ad absurdum Apr 19 '17 at 16:58
  • 2
    @doynax unfortunately _Generic does have some kinks... there have been commitee clarifications on it post-C11, e.g. whether array-pointer decay is applied to the argument, whether qualifiers are dropped from rvalue arguments – M.M Apr 19 '17 at 23:26
1

Yes, it is dangerous the way you do it, because you should not make assumptions about allocations. Likely if the array of pointers has pointers pointing to local variable – what is completely legal – you might have a layout equal to the layout of an array of ints.

You have to differ between two situations:

A. Dynamic evaluation

The C runtime is very small. Especially there is no runtime type information. Therefore it is not possible solely with the capabilities of the language, to get the type of an object that is referred at runtime.

B. Static evaluation

But your example looks like you want to determine the type of an object at compile time. C11 supports _Generic for that. It is like a switch, but not evaluating an ordinary expression having value cases, but working over types.

C11 Draft, 6.5.1.1

Amin Negm-Awad
  • 16,582
  • 3
  • 35
  • 50
0

I don't think there is reliably. If you know quite a lot about your compiler you might be able to infer

&a[0][0] will be 16 , 16*2 or maybe 16*4 bytes away from &a[3][3]

&b[0][0] will be 4, 4*2 or 4*4 bytes away from b[0][3]

leaving anything else to be c

But the complier would have to know to make them usable anyway so you most likely could not use them interchangeably without ignoring compile time warnings.

LoztInSpace
  • 5,584
  • 1
  • 15
  • 27
-2

By using sizeof() you can differentiate this variables, because sizeof() will give different size for different variables. Hope this helps you.

BusyProgrammer
  • 2,783
  • 5
  • 18
  • 31
  • This does not work, if the size of an array of "values" equals the size of an array of pointers, because the value type has the same size pointers have (I. e. on most platforms `long` and `whatevertype*`). – Amin Negm-Awad Apr 19 '17 at 05:04