C 从指向类型的指针到指向类型的数组的指针的转换是否安全?

C 从指向类型的指针到指向类型的数组的指针的转换是否安全?,c,arrays,pointers,type-conversion,C,Arrays,Pointers,Type Conversion,几天前,我偶然发现了一个代码,其中大量使用了从指针到类型到指针到类型数组的转换,以提供内存中线性向量的二维视图。为清楚起见,下文报告了此类技术的一个简单示例: #include <stdio.h> #include <stdlib.h> void print_matrix(const unsigned int nrows, const unsigned int ncols, double (*A)[ncols]) { // Here I can access m

几天前,我偶然发现了一个代码,其中大量使用了从指针到类型指针到类型数组的转换,以提供内存中线性向量的二维视图。为清楚起见,下文报告了此类技术的一个简单示例:

#include <stdio.h>
#include <stdlib.h>

void print_matrix(const unsigned int nrows, const unsigned int ncols, double (*A)[ncols]) {  
  // Here I can access memory using A[ii][jj]
  // instead of A[ii*ncols + jj]
  for(int ii = 0; ii < nrows; ii++) {
    for(int jj = 0; jj < ncols; jj++)
      printf("%4.4g",A[ii][jj]);
    printf("\n");
  }
}

int main() {

  const unsigned int nrows = 10;
  const unsigned int ncols = 20;

  // Here I allocate a portion of memory to which I could access
  // using linear indexing, i.e. A[ii]
  double * A = NULL;
  A = malloc(sizeof(double)*nrows*ncols);

  for (int ii = 0; ii < ncols*nrows; ii++)
    A[ii] = ii;

  print_matrix(nrows,ncols,A);
  printf("\n");
  print_matrix(ncols,nrows,A);

  free(A);
  return 0;
}
#包括
#包括
void print_矩阵(常数无符号整数nrows,常数无符号整数ncols,double(*A)[ncols]){
//在这里,我可以使用[ii][jj]访问内存
//而不是[ii*ncols+jj]
对于(int ii=0;ii

鉴于指向类型
指针与指向类型的数组的指针不兼容,我想问一下是否存在与此转换相关的风险,或者,如果我可以假设此转换在任何平台上都能按预期工作。

我的第一个想法是,C在创建2D数组时确实使用了该实现—也就是说,它线性地扩展内存:

[11, 12, 13, 14, 15, 21, 22, 23, 24, 25....] // This is known as ROW-MAJOR form
它在代码中的分配方式

A = malloc(rows*columns);
因此,我认为这样做没有什么害处,因为A是指向double的指针,“internal-C”实际上将A[][]转换为指向double的指针(注意:对于指向指针的指针!*),所以没有区别

* A = malloc ( rows ); for_each_Ai ( Ai = malloc (columns) );
^所有代码显然都是伪代码


关于平台独立性部分,该代码应该可以。但是,如果他们也在做其他鬼鬼祟祟的指针操作,那么要小心endian ness更新:删除线部分是正确的,但不相关

正如我在评论中所说,问题实际上是在二维数组中,子数组(行)是否包含内部填充。由于标准定义数组是连续的,因此每行中肯定不应有填充。此外,外部阵列不得引入填充物。事实上,通过浏览C标准,我发现在数组上下文中没有提到填充,因此我将“连续”解释为多维数组中的子数组末尾永远没有任何填充。由于
sizeof(array)/sizeof(array[0])
保证返回数组中的元素数,因此不能有这样的填充

这意味着
nrows
行和
ncol
列的多维数组的布局必须与
nrows*ncol
的一维数组的布局相同。因此,为了避免不兼容的类型错误,您可以

void *A = malloc(sizeof(double[nrows][ncols]));
// check for NULL

double *T = A;
for (size_t i=0; i<nrows*ncols; i++)
     T[i] = 0;
void*A=malloc(sizeof(double[nrows][ncols]);
//检查空值
双*T=A;

对于(size_t i=0;iC标准允许将指向对象(或不完整)类型的指针转换为指向不同对象(或不完整)类型的指针

不过,有几点需要注意:

  • 如果结果指针未正确对齐,则行为未定义。在这种情况下,标准不能保证这一点。但实际上,这不太可能

  • 标准只规定了结果指针的一种有效用法,即将其转换回原始指针类型。在这种情况下,标准保证后者(结果指针转换回原始指针类型)将与原始指针进行比较。将结果指针用于任何其他内容不在标准范围内

  • 标准在执行此类转换时需要显式强制转换,这在您发布的代码中的
    print\u矩阵
    函数调用中缺失


因此,根据该标准的规定,代码示例中的用法超出了其范围。但实际上,如果编译器允许,这可能在大多数平台上都能正常工作。

可以保证多维数组
T arr[M][N]
具有与具有相同元素总数的一维数组相同的内存布局。布局相同,因为数组是连续的(6.2.5p20),并且
sizeof array/sizeof array[0]
保证返回数组中的元素数(6.5.3.4p7)

但是,将指向类型的指针强制转换为指向类型数组的指针并不安全,反之亦然。首先,对齐是一个问题;尽管具有基本对齐的类型数组也必须具有基本对齐(按6.2.8p2)不能保证对齐方式相同。因为数组包含基本类型的对象,所以数组类型的对齐方式必须至少与基本对象类型的对齐方式一样严格,但可以更严格(我从未见过这种情况)。但是,这与分配的内存无关,因为
malloc
保证返回为任何基本对齐(7.22.3p1)适当分配的指针。这意味着您无法将指向自动或静态内存的指针安全地强制转换为数组指针,尽管允许反向操作:

int a[100];
void f() {
    int b[100];
    static int c[100];
    int *d = malloc(sizeof int[100]);
    int (*p)[10] = (int (*)[10]) a;  // possibly incorrectly aligned
    int (*q)[10] = (int (*)[10]) b;  // possibly incorrectly aligned
    int (*r)[10] = (int (*)[10]) c;  // possibly incorrectly aligned
    int (*s)[10] = (int (*)[10]) d;  // OK
}

int A[10][10];
void g() {
    int B[10][10];
    static int C[10][10];
    int (*D)[10] = (int (*)[10]) malloc(sizeof int[10][10]);
    int *p = (int *) A;  // OK
    int *q = (int *) B;  // OK
    int *r = (int *) C;  // OK
    int *s = (int *) D;  // OK
}
其次,根据强制转换规则(6.3.2.3p7),不能保证在数组和非数组类型之间强制转换实际上会导致指向正确位置的指针不要涉及此用法。这不太可能导致指向正确位置的指针以外的任何结果,并且通过
char*
进行强制转换确实具有保证的语义。当从指针到数组类型再到指针到基类型时,最好只间接指向指针:

void f(int (*p)[10]) {
    int *q = *p;                            // OK
    assert((int (*)[10]) q == p);           // not guaranteed
    assert((int (*)[10]) (char *) q == p);  // OK
}
什么
int a[100];
((int (*)[10]) a) + 3;    // invalid - no int[10][N] array

int b[10][10];
(*b) + 3;          // OK
(*b) + 23;         // invalid - out of bounds of int[10] array
int a[10][10];
void f(int n) {
    for (int i = 0; i < n; ++i)
        (*a)[i] = 2 * a[2][3];
}
int a[10][10];
void f_optimised(int n) {
    int intermediate_result = 2 * a[2][3];
    for (int i = 0; i < n; ++i)
        (*a)[i] = intermediate_result;
}