Referencing a yet-to-be-mentioned function parameter using the new-style function declarations
Asked Answered
C

2

6

Compelled to use the variable length array feature for my auxiliary function that prints square matrices, I defined it as follows:

void print_matrix(M, dim)
     unsigned dim;
     int M[dim][dim];
{
    /* Print the matrix here. */
    ...

The good news is, the code works and has its parameters in the order I'd like them to be.

The bad news is, I had to use the "old-style" function declaration syntax in order to reference the yet-to-be-declared argument dim in the declaration of M, which is apparently considered obsolete and dangerous.

Is there a straightforward way to do the same with the "new-style" function declarations WITHOUT changing the order of the parameters? (And if not, is it considered acceptable use of the old-style syntax in this particular situation?)

Cathode answered 27/3, 2019 at 23:55 Comment(12)
You can't; you specify the dimension before the matrix: void print_matrix(unsigned dim, int (*M)[dim][dim]). You're printing a 3D array, of course.Hobson
Perhaps you can use a void pointer in the parameter list and assign it to the correct pointer type in the function.Roofer
I suspect that if you specified -Wpedantic-errors, you'd get compilation errors. Certainly, K&R compilers did not support VLA notation. There's a moderate chance you're using GCC and it is providing extensions that allow it to work.Hobson
@JonathanLeffler Both Clang(6.0.0) and GCC(7.3.0) issued no complaints about my code, even with -std=c11 -pedantic-errors, but great point about the compatibility, of course. Even if the standard does not explicitly prohibit it (or so it seems), it's probably going to be poorly tested on the GCC/Clang side and non-idiomatic. I'm just surprised you can do that at all, to be honest.Cathode
I'm surprised too. At one level, it makes sense. I don't spend much time worrying about what compilers can do with extensions over K&R with K&R function definitions. I aim to work in portable C, and accept that I have to put the size parameters before the arrays that use them in the prototype notation.Hobson
@JonathanLeffler: There is nothing non-portable about using a combination of prototypes and old-style declarations.Petrie
@Petrie — K&R style function definitions are obsolescent, and have been marked as such in the standard even in C90. They will probably be removed from C2x (see standards documents N2432 and N2478 at open-std.org/jtc1/sc22/wg14), whereupon they become non-standard, though compilers will continue to support the then non-standard notation for many years (just as libraries still contain a working gets() function even though it has not been part of the standard since 2011, and was marked deprecated in TC3 for C99).Hobson
@JonathanLeffler: They were obsolescent in C89, but became relevant in C99 because they can properly accommodate functions like the one given which new-style declarations cannot. Unfortunately, rather than recognize the inability of new-style declarations to handle functions whose arguments follow long-standing conventions as a defect in C99, it seems the Committee would rather brand as defective code which new-style declarations failed to accommodate, for (judging from the Rationale) no reason other than Committee laziness.Petrie
@JonathanLeffler: If the Committee were to specify that arrays within an argument list that have non-constant size will be treated as having incomplete type until after the entire list has been processed, and that array sizes will be processed after all of the objects within the argument list are processed, that would allow the long-standing practices to be supported with new-style function declarations, without requiring significant extra work in the compiler (the extra work would be a tiny fraction of what's needed to support VLAs in general).Petrie
@Petrie — I don't think your characterizations of the committee are fair, and we're going to have to agree to disagree. I regard this matter as closed — until we're both on the Standard C committee (which I'm not at the moment, and I have no plans to change that status).Hobson
@JonathanLeffler: The language described in the 1974 C Reference Manual could be on many platforms be practically accommodated by a single-pass compiler that generated machine code as it processed source code, requiring nothing more than address fix-ups that could be accommodated at link time. Properly handling all the corner cases of new C99 features in such a compiler would be just about impossible, however, even though few non-contrived programs would care about the difficult corner cases. If a practicality of single-pass compilation has been abandoned, why use it as an excuse...Petrie
@JonathanLeffler: Rereading the Rationale, I guess maybe "laziness" isn't fair, but I'm not sure how I'd characterize 'Such a change to the lexical ordering rules is not considered to be in the “Spirit of C,” however.' Performing array-size computations between argument-list processing and function-body processing would "complicate" the language far less than other VLA-related changes like making sizeof no longer constant, and decreeing that code which puts size parameters after array pointers is somehow defective flies directly in the face of the first two Spirit of C principles.Petrie
H
3

In portable (standard) C, you can't do what you show. You have to specify the dimension before the matrix. The original code in the question was:

void print_matrix(M, dim)
     unsigned dim;
     int (*M)[dim][dim];
{

and that can't be directly translated — it needs a prototype like this, with the dimension before the matrix:

void print_matrix(unsigned dim, int (*M)[dim][dim]);

This allows you to call the function with a 3D array. Or, with the revised notation in the question, you can print a 2D array:

void print_matrix(unsigned dim, int M[dim][dim]);

GCC provides an extension to assist. Quoting the manual:

If you want to pass the array first and the length afterward, you can use a forward declaration in the parameter list—another GNU extension.

struct entry
tester (int len; char data[len][len], int len)
{
  /* … */
}

You can write any number of such parameter forward declarations in the parameter list. They can be separated by commas or semicolons, but the last one must end with a semicolon, which is followed by the “real” parameter declarations. Each forward declaration must match a “real” declaration in parameter name and data type. ISO C99 does not support parameter forward declarations.

Hobson answered 28/3, 2019 at 0:6 Comment(5)
Using a combination of a prototype and an old-style declaration will avoid the need to throw long-standing argument-ordering conventions out the window.Petrie
@supercat: what do you think the prototype looks like? The only ones that could work would be void print_matrix(int M[][*], unsigned dim) and void print_matrix(int M[*][*], unsigned dim). Interestingly, GCC seems to accept either notation, even with -pedantic; so does clang though with -Weverything it objects to the use of the VLA at all, simply on the grounds that it is a VLA ([-Werror,-Wvla]). […continued…]Hobson
[…continuation…] So, if you're prepared to write the function definition in the obsolescent style, using a feature added to the language after the style was declared obsolescent, then "yes, you can write the function definition K&R style and declare it with a prototype". I don't write functions K&R style any more, and I spend far too much time cleaning up a code base littered with them still (though the amount of litter has decreased dramatically recently — and bugs have been found). I won't ever advise anyone to write K&R style functions unless there is extreme duress applied to me.Hobson
The prototypes using [*][*] are perfectly valid, and I'm not quite sure what objection you have to them. Things sometimes go from being obsolete to being necessary as a result of technological developments, and while there was never any reason other than standard-writer laziness for C99 to have made it necessary to use that syntax. In C89, the same caller-side semantics could be achieved with void test(double(*twodarr)[], int rows, int cols) {...} which could then cast the argument to a double* and perform pointer arithmetic on it as appropriate.Petrie
Being able to have a called function use a 2D array without the extra casting step would be nice, but I don't see that as any justification for requiring that functions using the new syntax be called differently. Since compilers have no need to know or care about the array sizes in the parameter list until the entire list had been parsed, all that would be necessary would be to have the compiler buffer up the size arguments and then evaluate them after parsing the declarations, and that's trivial compared with some of the other gymnastics forced upon compilers by clumsy corner cases in C99.Petrie
P
4

An old-style declaration which is preceded by a prototype is syntactically ugly, but is no more dangerous than a new style declaration.

void print_matrix(int M[*][*], unsigned dim);
void print_matrix(M, dim)
     unsigned dim;
     int M[dim][dim];
{
  ...
}

The authors of the Standard recognized that the old-style declarations were a valid, useful, and sufficient means of accomplishing the task, and decided that writing rules to allow with new-style declarations would be too much work, so they didn't want to bother. The combination of a prototype and old-style declaration is thus the only way to achieve the appropriate semantics.

Petrie answered 10/4, 2019 at 23:12 Comment(9)
Unfortunately, I've heard that K&R-style declarations will be removed in the next C standard. Are you aware of any solution proposed for this kind of declaration?Muscovado
@fuz: I'm not. On the other hand, given that I dislike VLAs anyway, it's way down on my list of complaints. A far bigger problem is the Standard's failure to clearly state its jurisdiction. Imagine how much time could have been saved if the Standard had, in describing Undefined Behavior, said "In all such cases, the Standard waives jurisdiction over program behavior so as to allow implementations designed for various purposes to process code in whatever manner would best serve those purposes." For the vast majority of common questions about whether the Standard defines something...Petrie
...the correct answer would be that it doesn't, because implementations intended for purposes where that construct would be useful would define it without regard for whether the Standard required them to do so. Unfortunately, compiler writers who promote myths about the purpose of UB will almost certainly veto any addition to the Standard that would contradict their myths.Petrie
Are you related to user flatfinger on reddit? You certainly sound similar.Muscovado
@fuz: That's me.Petrie
The world is small I guess. (You might know me as /u/FUZxxl)Muscovado
@fuz: Yeah. Sorry if I come across as antagonistic sometimes. It irks me that the Standard has for decades created needless strife between the viewpoints "all compilers must support X" and "programmers must never do X", when the real situation has always been "Compilers intended for purposes where X would be useful should support it, and programmers should only do X when targeting compilers intended for such purposes". If some tasks could be accomplished most efficiently on an implementation that doesn't doesn't support X, but others could be accomplished most effectively by doing X...Petrie
...the Standard should make clear that it makes no attempt to say whether or not any particular implementation should support X.Petrie
I don't view you as antagonistic. Discussion with you is always interesting, but I would appreciate if you could shorten the the introduction of your point of view (which I'm already familiar with).Muscovado
H
3

In portable (standard) C, you can't do what you show. You have to specify the dimension before the matrix. The original code in the question was:

void print_matrix(M, dim)
     unsigned dim;
     int (*M)[dim][dim];
{

and that can't be directly translated — it needs a prototype like this, with the dimension before the matrix:

void print_matrix(unsigned dim, int (*M)[dim][dim]);

This allows you to call the function with a 3D array. Or, with the revised notation in the question, you can print a 2D array:

void print_matrix(unsigned dim, int M[dim][dim]);

GCC provides an extension to assist. Quoting the manual:

If you want to pass the array first and the length afterward, you can use a forward declaration in the parameter list—another GNU extension.

struct entry
tester (int len; char data[len][len], int len)
{
  /* … */
}

You can write any number of such parameter forward declarations in the parameter list. They can be separated by commas or semicolons, but the last one must end with a semicolon, which is followed by the “real” parameter declarations. Each forward declaration must match a “real” declaration in parameter name and data type. ISO C99 does not support parameter forward declarations.

Hobson answered 28/3, 2019 at 0:6 Comment(5)
Using a combination of a prototype and an old-style declaration will avoid the need to throw long-standing argument-ordering conventions out the window.Petrie
@supercat: what do you think the prototype looks like? The only ones that could work would be void print_matrix(int M[][*], unsigned dim) and void print_matrix(int M[*][*], unsigned dim). Interestingly, GCC seems to accept either notation, even with -pedantic; so does clang though with -Weverything it objects to the use of the VLA at all, simply on the grounds that it is a VLA ([-Werror,-Wvla]). […continued…]Hobson
[…continuation…] So, if you're prepared to write the function definition in the obsolescent style, using a feature added to the language after the style was declared obsolescent, then "yes, you can write the function definition K&R style and declare it with a prototype". I don't write functions K&R style any more, and I spend far too much time cleaning up a code base littered with them still (though the amount of litter has decreased dramatically recently — and bugs have been found). I won't ever advise anyone to write K&R style functions unless there is extreme duress applied to me.Hobson
The prototypes using [*][*] are perfectly valid, and I'm not quite sure what objection you have to them. Things sometimes go from being obsolete to being necessary as a result of technological developments, and while there was never any reason other than standard-writer laziness for C99 to have made it necessary to use that syntax. In C89, the same caller-side semantics could be achieved with void test(double(*twodarr)[], int rows, int cols) {...} which could then cast the argument to a double* and perform pointer arithmetic on it as appropriate.Petrie
Being able to have a called function use a 2D array without the extra casting step would be nice, but I don't see that as any justification for requiring that functions using the new syntax be called differently. Since compilers have no need to know or care about the array sizes in the parameter list until the entire list had been parsed, all that would be necessary would be to have the compiler buffer up the size arguments and then evaluate them after parsing the declarations, and that's trivial compared with some of the other gymnastics forced upon compilers by clumsy corner cases in C99.Petrie

© 2022 - 2024 — McMap. All rights reserved.