C 函数 const 多维数组参数中的奇怪警告

发布于 2024-10-10 12:49:38 字数 2137 浏览 6 评论 0原文

我收到一些关于此代码的奇怪警告:

typedef double mat4[4][4];

void mprod4(mat4 r, const mat4 a, const mat4 b)
{
/* yes, function is empty */
}

int main()
{
    mat4 mr, ma, mb;
    mprod4(mr, ma, mb);
}

gcc 输出如下:

$ gcc -o test test.c
test.c: In function 'main':
test.c:13: warning: passing argument 2 of 'mprod4' from incompatible pointer
type
test.c:4: note: expected 'const double (*)[4]' but argument is of type 'double
(*)[4]'
test.c:13: warning: passing argument 3 of 'mprod4' from incompatible pointer
type
test.c:4:
note: expected 'const double (*)[4]' but argument is of type 'double
(*)[4]'

如果我将函数定义为:

void mprod4(mat4 r, mat4 a, mat4 b)
{
}

或在 main 中定义矩阵为:

mat4 mr;
const mat4 ma;
const mat4 mb;

或在 main 中调用函数为:

mprod4(mr, (const double(*)[4])ma, (const double(*)[4])mb);

甚至定义 mat4 as:

typedef double mat4[16];

使警告消失。这里发生了什么?我是不是在做一些无效的事情?

gcc 版本是 4.4.3(如果相关)。

我还在 gcc bugzilla 上发布了: http://gcc.gnu.org/bugzilla /show_bug.cgi?id=47143

我当前的解决方法是制作丑陋的宏来为我投射东西:

#ifndef _NO_UGLY_MATRIX_MACROS

#define mprod4(r, a, b) mprod4(r, (const double(*)[4])a, (const double(*)[4])b)

#endif

Joseph S. Myers 在 gcc bugzilla 上的回答:

不是错误。函数参数 属于“指向 array[4] 的指针”类型 const double”因为 const 位于 数组类型适用于元素 递归地键入,然后 仅最外层数组类型 数组类型的参数衰减为 指针,传递的参数是 类型为“指向数组[4]的指针” double” 数组到指针衰减后, 唯一的情况是限定符是 允许添加到作业中, 参数传递等是限定符 立即指针目标,而不是 那些嵌套得更深。

对我来说听起来很混乱,就像函数所期望的那样:

pointer to array[4] of const doubles

我们正在传递

pointer to const array[4] of doubles

或者是相反的情况?警告表明该函数需要 a:

const double (*)[4]

在我看来更像是 a

pointer to const array[4] of doubles

我真的对这个答案感到困惑。有懂的人可以解释一下并举例说明吗?

I'm getting some strange warnings about this code:

typedef double mat4[4][4];

void mprod4(mat4 r, const mat4 a, const mat4 b)
{
/* yes, function is empty */
}

int main()
{
    mat4 mr, ma, mb;
    mprod4(mr, ma, mb);
}

gcc output as follows:

$ gcc -o test test.c
test.c: In function 'main':
test.c:13: warning: passing argument 2 of 'mprod4' from incompatible pointer
type
test.c:4: note: expected 'const double (*)[4]' but argument is of type 'double
(*)[4]'
test.c:13: warning: passing argument 3 of 'mprod4' from incompatible pointer
type
test.c:4:
note: expected 'const double (*)[4]' but argument is of type 'double
(*)[4]'

If I define the function as:

void mprod4(mat4 r, mat4 a, mat4 b)
{
}

Or defining matrices in main as:

mat4 mr;
const mat4 ma;
const mat4 mb;

Or call the function in main as:

mprod4(mr, (const double(*)[4])ma, (const double(*)[4])mb);

Or even defining mat4 as:

typedef double mat4[16];

Makes the warning go away. What is happening here? Am I doing something invalid?

The gcc version is 4.4.3, if relevant.

I also posted on gcc bugzilla: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=47143

My current workaround is making ugly macros that cast stuff for me:

#ifndef _NO_UGLY_MATRIX_MACROS

#define mprod4(r, a, b) mprod4(r, (const double(*)[4])a, (const double(*)[4])b)

#endif

Answer from Joseph S. Myers on gcc bugzilla:

Not a bug. The function parameters
are of type "pointer to array[4] of
const double" because const on an
array type applies to the element
type, recursively, and then the
outermost array type, only, of a
parameter of array type decays to a
pointer, and the arguments passed are
of type "pointer to array[4] of
double" after array-to-pointer decay,
and the only case where qualifiers are
permitted to be added in assignment,
argument passing etc. is qualifiers on
the immediate pointer target, not
those nested more deeply.

Sounds pretty confusing to me, like the function expects:

pointer to array[4] of const doubles

and we are passing

pointer to const array[4] of doubles

intead.

Or would it be the inverse? The warnings suggest that the function expects a:

const double (*)[4]

which seems to me more like a

pointer to const array[4] of doubles

I'm really confused with this answer. Could somebody who understands what he said clarify and exemplify?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

瘫痪情歌 2024-10-17 12:49:38

我认为问题在于 C99 6.5.16.1(1) 中指定的约束,这些约束似乎禁止在赋值中混合限定,但包含限定符异常的指针除外被定义。问题在于,使用间接指针时,您最终会将指向一个事物的指针传递给指向另一个事物的指针。该赋值无效,因为如果有效,您可以使用以下代码欺骗它修改 const 限定对象:

const char **cpp;
char *p;
const char c = 'A';
cpp = &p;  // constraint violation
*cpp = &c; // valid
*p = 0;    // valid by itself, but would clobber c

cpp 看起来似乎很合理,它承诺不修改任何 char 可能会被分配一个指向指向非限定 char 的对象的指针。毕竟,这是允许单间接指针的,这就是为什么,例如,您可以将可变对象传递给 strcpy(3) 的第二个参数,将第一个参数传递给 strchr(3 ),以及使用 const 声明的许多其他参数。

但是对于间接指针,在下一个级别,允许从合格的指针进行赋值,现在完全不合格的指针赋值将破坏合格的对象。

我没有立即看出二维数组如何导致这种情况,但无论如何它都会遇到标准中的相同约束。

由于在您的情况下,您实际上并没有欺骗它破坏 const,因此您的代码的正确做法似乎是插入强制转换。


更新:好的,伙计们,这个问题发生在 C faq,整个讨论也进行了多次在 gcc 错误列表 和 gcc 邮件列表上。

教训:当需要 const T *x 时,您可以通过显式异常传递 T *x,但是 T *xconst T *x 仍然是不同的类型,因此您不能将指向其中之一的指针传递给指向另一个的指针。

I believe the problem is the constraints specified in C99 6.5.16.1(1), which seem to prohibit mixing qualifications in assignments, except for pointers for which an inclusive-qualifier exception is defined. The problem is that with indirect pointers, you end up passing a pointer to one thing to a pointer to another. The assignment isn't valid because, if it was, you could fool it into modifying a const-qualified object with the following code:

const char **cpp;
char *p;
const char c = 'A';
cpp = &p;  // constraint violation
*cpp = &c; // valid
*p = 0;    // valid by itself, but would clobber c

It might seem reasonable that cpp, which promises not to modify any chars, might be assigned a pointer to an object pointing at non-qualified chars. After all, that's allowed for single-indirect pointers, which is why, e.g., you can pass a mutable object to the second parameter of strcpy(3), the first parameter to strchr(3), and many other parameters that are declared with const.

But with the indirect pointer, at the next level, assignment from a qualified pointer is allowed, and now a perfectly unqualified pointer assignment will clobber a qualified object.

I don't immediately see how a 2-D array could lead to this situation, but in any case it hits the same constraint in the standard.

Since in your case, you aren't actually tricking it into clobbering a const, the right thing for your code would seem to be inserting the cast.


Update: OK guys, as it happens this issue is in the C faq, and this entire discussion has also taken place several times on the gcc bug list and on the gcc mailing list.

The lesson: you can pass a T *x when const T *x is expected, by explicit exception, but T *x and const T *x are still distinct types, so you can't pass a pointer to either one to a pointer to the other.

我做我的改变 2024-10-17 12:49:38

解释一下 Joseph 所说的:该函数期望传入一个指向 const double 的 array[4] 的指针,但您传入的是一个指向 double 的 array[4] 的指针。这些类型不兼容,因此您会收到错误。它们看起来应该兼容,但事实并非如此。

为了将参数传递给函数(或变量赋值),您始终可以将 X 转换为 const X指向 X 的指针 code> 指向任何类型 Xconst X 指针。例如:

int x1 = 0;
const int x2 = x1;  // ok
int *x3 = &x1;
const int *x4 = x3;  // ok: convert "pointer to int" to "pointer to const int"
int **x5 = &x3;
const int **x6 = x5;  // ERROR: see DigitalRoss's answer
int *const *x7 = x5;  // ok: convert "pointer to (pointer to int)" to
                      //             "pointer to const (pointer to int)"

您只能向 添加限定符(即 constvolatilerestrict 限定符)第一层指针。您无法将它们添加到更高级别的指针,因为 DigitalRoss 提到,这样做会让你意外地违反常量正确性。这就是约瑟夫所说的“唯一的情况
允许在赋值、参数传递等中添加限定符
直接指针目标上的限定符,而不是嵌套得更深的限定符。”

因此,让我们回到 Joseph 的响应,您无法将指向 double 的 array[4] 的指针转换为指针到 const double 的 array[4] ,因为没有类型 X ,因此您可以从 指向 X 的指针 转换为 指向 const X 的指针

如果您尝试将 array[4] of double 用于 X,您会发现可以转换为 >指向 double 的 const array[4] 的指针,这是一种不同的类型,但是 C 中不存在这种类型:您可以拥有一个 const 类型的数组,但确实有。没有 const 数组这样的东西,

因此,没有办法完美解决您的问题,您必须向所有函数调用添加强制转换(手动或通过宏或辅助函数)。 ),重写您的函数以不采用 const 参数(不好,因为它不允许您传入 const 矩阵),或者更改 mat4类型为一维数组或结构体,如 用户502515建议

To explain what Joseph said: the function is expecting a pointer to array[4] of const double to be passed in, but you're passing in a pointer to array[4] of double. These types are not compatible, so you get an error. They look like they should be compatible, but they're not.

For the purposes of passing parameters to functions (or for variable assignments), you can always convert an X to a const X, or a pointer to X to a pointer to const X for any type X. For example:

int x1 = 0;
const int x2 = x1;  // ok
int *x3 = &x1;
const int *x4 = x3;  // ok: convert "pointer to int" to "pointer to const int"
int **x5 = &x3;
const int **x6 = x5;  // ERROR: see DigitalRoss's answer
int *const *x7 = x5;  // ok: convert "pointer to (pointer to int)" to
                      //             "pointer to const (pointer to int)"

You're only allowed to add qualifiers (that is, the const, volatile, and restrict qualifiers) to the first level of pointers. You can't add them to higher levels of pointers because, as DigitalRoss mentioned, doing so would allow you to accidentally violate const-correctness. This is what Joseph means by "the only case where
qualifiers are permitted to be added in assignment, argument passing etc. is
qualifiers on the immediate pointer target, not those nested more deeply."

So, bringing us back to Joseph's response, you can't convert a pointer to array[4] of double to a pointer to array[4] of const double because there is no type X such that you're converting from pointer to X to pointer to const X.

If you try using array[4] of double for X, you'd see that you can convert to pointer to const array[4] of double, which is a different type. However, no such type exists in C: you can have an array of a const type, but there is no such thing as a const array.

Hence, there's no way to perfectly solve your problem. You'll have to either add casts to all of your function calls (either manually or via a macro or helper function), rewrite your functions to not take const parameters (bad since it doesn't let you pass in const matrices), or change the mat4 type to be either a 1-dimensional array or a structure, as user502515 suggested.

一世旳自豪 2024-10-17 12:49:38

为了实际解决这个问题,可以使用一个结构体,并将 double[4][4] 更改为有点尴尬的 double (*)[4]避免了这种情况,并且常量也直观地发挥作用——同时使用相同数量的内存:

struct mat4 {
        double m[4][4];
};

void myfunc(struct mat4 *r, const struct mat4 *a, const struct mat4 *b)
{
}

int main(void)
{
        struct mat4 mr, ma, mb;
        myfunc(&mr, &ma, &mb);
}

To practically solve this, one could use a struct, and the change of double[4][4] into the a-bit-awkward double (*)[4] is avoided, and constness also works intuitively — while the same amount of memory is used:

struct mat4 {
        double m[4][4];
};

void myfunc(struct mat4 *r, const struct mat4 *a, const struct mat4 *b)
{
}

int main(void)
{
        struct mat4 mr, ma, mb;
        myfunc(&mr, &ma, &mb);
}
风蛊 2024-10-17 12:49:38

我认为在 C99 中,你可以这样做,但我不确定它会有帮助:

void mprod4(double mr[4][4], double ma[const 4][const 4], double mb[const 4][const 4])
{

}

我手边没有 C99 编译器,但我记得读过 C99 规范中有关 [] 对于数组作为参数。您还可以在其中放置static(例如ma[static 4]),但这当然意味着其他含义。

编辑

这是第 6.7.3.5 节第 7 段。

将参数声明为“类型数组”应调整为“指向类型的限定指针”,其中类型限定符(如果有)是在 [中指定的类型限定符] 数组类型推导。如果关键字 static 也出现在数组类型派生的 [] 中,则对于该函数的每次调用,相应的实际参数应提供对数组第一个元素的访问,该数组的元素数量至少与大小表达式指定的元素数量相同。

I think in C99, you can do this, but I'm not sure it will help:

void mprod4(double mr[4][4], double ma[const 4][const 4], double mb[const 4][const 4])
{

}

I haven't got a C99 compiler handy but I remember reading something in the C99 specification regarding qualifiers within the [] for arrays as arguments. You can also put static in there (e.g. ma[static 4]) but of course that means something else.

Edit

Here it is, section 6.7.3.5 paragraph 7.

A declaration of a parameter as “array of type” shall be adjusted to “qualified pointer to type”, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

葬﹪忆之殇 2024-10-17 12:49:38

这是一个问题(恕我直言):函数签名中的 double[4][4]

知道它是一个double[4][4],但编译器在函数参数列表中看到double(*)[4],值得注意的是,它没有数组大小限制。它将 4 x 4 对象的 2D 数组转换为指向 4 个对象的 1D 数组的指针,并且该指针可以有效索引,就像它是 4 个对象的数组一样。

我将通过指针传递所有 mat4 对象:

void mprod4(mat4 *r, const mat4 *a, const mat4 *b);
// and, if you don't want to hairy your syntax
#define mprod4(r, a, b) (mprod4)(&r, (const mat4 *)&a, (const mat4 *)&b)

这将(我相信)确保 const 正确性和数组大小正确性。它可能会使 mprod4 更难编写,并且仍然涉及一些毛茸茸的演员,但它(恕我直言)是值得的(特别是在上面的宏之后):

void mprod4(mat4 *r, const mat4 *a, const mat4 *b)
{
    // all indexing of the matricies must be done after dereference
    for(int i = 0; i < 4; i++) for(int j = 0; j < 4; j++)
      {
        (*r)[i][j] = (*a)[i][j] * (*b)[i][j];
        // you could make it easier on yourself if you like:
        #define idx(a, i, j) ((*a)[i][j])
        idx(r, i, j) = idx(a, i, j) * idx(b, i, j)
      }
}

当你写它,但我认为它在类型上会更清晰。 (也许我对C++想太多了......)

Here's a problem (IMHO): double[4][4] in a function signature.

You know it's a double[4][4], but the compiler sees double(*)[4] in the function paramter list, which notably has no array size constraint. It turns your 2D array of 4 by 4 objects into a pointer to a 1D array of 4 objects, and the pointer can be validly indexed as if it were an array of 4 objects.

I would pass all mat4 objects by pointer:

void mprod4(mat4 *r, const mat4 *a, const mat4 *b);
// and, if you don't want to hairy your syntax
#define mprod4(r, a, b) (mprod4)(&r, (const mat4 *)&a, (const mat4 *)&b)

This will (I believe) ensure const correctness and array size correctness. It may make mprod4 a bit harder to write, and still involves some hairy casts, but it'll (IMHO) be worth it (especially after the macro above):

void mprod4(mat4 *r, const mat4 *a, const mat4 *b)
{
    // all indexing of the matricies must be done after dereference
    for(int i = 0; i < 4; i++) for(int j = 0; j < 4; j++)
      {
        (*r)[i][j] = (*a)[i][j] * (*b)[i][j];
        // you could make it easier on yourself if you like:
        #define idx(a, i, j) ((*a)[i][j])
        idx(r, i, j) = idx(a, i, j) * idx(b, i, j)
      }
}

It may look a bit bad when you write it, but I think it'll be cleaner type-wise. (Maybe I've been thinking C++ too much...)

我很OK 2024-10-17 12:49:38

编译器只是肛门。

您传递的参数本质上是非常量指针,并且该函数被声明为接受常量指针作为参数。事实上,这两者是不相容的。这不是一个真正的问题,因为只要您可以将第一种类型的值分配给第二种类型的变量,编译器仍然应该工作。因此是警告但不是错误。

编辑:看起来 gcc 不会抱怨其他 con-const 到 const 转换,例如在需要 const char* 的地方传递 char* 。在这种情况下,我倾向于同意 Bugzilla 的 Joseph Myers 是正确的。

Compiler is just being anal.

You're passing an argument that is essentially a non-const pointer, and the function is declared to accept a const pointer as an argument. These two are, in fact, incompatible. It is not a real problem because the compiler is still supposed to work as long as you can assign the value of the first type to the variable of the second type. Hence a warning but not an error.

EDIT: looks like gcc does not complain about other con-const to const conversions, e.g. passing char* where a const char* is expected. In this case, I'm inclined to agree that Joseph Myers from Bugzilla is correct.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文