【问题标题】:allocate a float vector with subscript range v[nl..nh]分配一个下标范围为 v[nl..nh] 的浮点向量
【发布时间】:2019-12-17 10:13:33
【问题描述】:

好的,我想了解它的作用:

#define NR_END 1

float *vector(long nl, long nh)
/* allocate a float vector with subscript range v[nl..nh] */
{
    float *v;

    v=(float *)malloc((size_t) ((nh-nl+1+NR_END)*sizeof(float)));
    if (!v) nrerror("allocation failure in vector()");
    return v-nl+NR_END;
}

我的意思是它创建了一个指向给定大小的 malloc 空间的指针。所以 v[0] 是第一个单元……以此类推直到块结束。

但是为什么最后返回了一些术语。

我以为你不能用指针移动堆栈上的任何地方,而只能访问给定的空间?

int main()
{
    printf("Hello world\n\n");

    float* f = vector(3,5);

    f[0]=1;
    f[1]=2;
    f[2]=3;
    f[7] = 5;


    printf("%f", f[7]);

    return 0;

}

我做了这些测试,当 gcc filename.c -o filename 编译时没有错误或警告。

为什么 v[7]=5 在不应该工作的情况下工作?

什么是下标范围?

你应该如何正确使用它?

我很困惑。请帮助我理解这一点。

该文件似乎也可以在 https://www.cfa.harvard.edu/~sasselov/rec/code/nrutil.c 上查看

【问题讨论】:

  • 请注意,C 和 C++ 是两种截然不同的语言,具有不同的规则和语义。因此,请不要在您的问题中使用“C/C++”之类的术语或标记两种语言。这就是为什么 C++ 标记已被删除,因为它似乎不适用(因为您正在使用 C 编程)。
  • 至于你的部分问题。 C没有任何边界检查。作为程序员,您有责任不超出分配的内存范围。越界会导致未定义的行为
  • 我们告诉了你它的作用和链接。这是未定义的行为,因为访问越界。这意味着我们实际上无法确定结果会是什么。
  • 什么是NR_END?在确定vector 函数的返回值将是什么时,这似乎是关键 - 大概,它将总是 > nl,所以计算的返回值将从不小于v

标签: c pointers malloc


【解决方案1】:

这是一些看起来很粗略的代码,这不是一个好主意。

函数似乎分配了一个向量,调用者承诺只使用封闭区间 [nl, nh] 中的索引,例如vector(100, 104) 将尝试分配索引为 100、101、102、103 和 104 的 5 元素向量。任何其他索引都会导致未定义的行为。这包括0,它通常是 C 数组的第一个有效索引。

两行核心代码是:

// 1
v=(float *)malloc((size_t) ((nh-nl+1+NR_END)*sizeof(float)));

如果没有毫无意义的强制转换,这可以重写得更简洁:

v = malloc((nh - nl + 1 + NR_END) * sizeof *v);

然后计算所需索引间隔的大小(nh - nl + 1 将导致 104 - 100 + 1 在我们的示例中为 5)。它添加了一个额外的NR_END 元素,不知道为什么。

然后是:

// 2
return v - nl + NR_END;

这将返回基本指针v,但首先通过向后移动nl 元素来调整它,即在我们的示例中为100。这会导致100 的索引命中第一个实际分配的元素。 NR_END 的添加使分配向量的使用偏向最后,我也不知道为什么。

所以在内存中它看起来像这样,分配了 6 个元素:

   +---+---+---+---+---+---+
v: | 0 | 1 | 2 | 3 | 4 | 5 |
   +---+---+---+---+---+---+

但是通过减法,我们返回一个指向低地址的指针,依赖调用者索引至少nl 来备份到分配的空间。

说了这么多,我很确定这是未定义的行为,并且对地址计算做了一些假设。对于 N 个元素的数组,您不应该使用 [0,N] 范围之外的地址,这将排除在数组开始之前处理地址。

【讨论】:

  • 优秀的答案!和你一样,我也不确定为什么要添加额外的 (NR_END) 元素。另外,我认为这是来自“C 中的数字食谱”一书/代码的“传统 K&R”版本 - 非常古老且容易被滥用。
  • 哦,所以他故意返回了一些错误的指针(这实际上是一种好的做法还是有更好的方法来做到这一点?)所以当你这样做时*(p+nl)你实际上是在正确的位置你应该有的地方。 NR_END 并没有真正的意义,如果没有我认为它会起作用。但是由于他在返回时使用它,我认为他让创建的数组的第一个元素为空,并且您访问第 [101] 个索引,这意味着如果您执行 v[100] 而不是第 [2] 个索引第一个?
【解决方案2】:

vector(long nl, long nh) 函数所做的是创建一个必要大小的数组来保存给定的数字范围,并添加一个“额外”元素(可能是为了允许C 获取地址的做法)一个超出范围”)。因此,如果您使用35 的值调用它,它会在v 的地址为4 float 变量分配空间。

然而,仅仅使用这个v 地址,就必须使用02 范围内的索引来访问元素。因此,在返回地址之前,该函数会从中减去您的下索引 (nl)。现在,虽然这个值现在将指向一个无效的内存地址,但如果你只访问指定范围内的元素,那么用于计算的指针算法,比如main函数中的f[3],将添加一个合适的偏移量到这个“无效”指针,以便您实际上访问v[0]变量。

【讨论】:

  • 你的意思是 0 到 3,因为数组中有一个额外的元素?
  • 是也不是!我不完全理解为什么要添加额外的元素,但是,坚持要求的范围,3 ... 5 的索引将“等于” 0 ... 2 我在回答中提到它。
  • 我在想也许他在数组中留下了一个额外的元素,因为他返回 smth + NR_END,这意味着当你有 vector(100,104) 并且你做 v[100] 时,你实际上去了第二位在 malloc 创建的数组中。意思是,如果你做 v[99] 它也是正确的,并且会访问数组的第一个元素,这没有意义为什么你想要那个
【解决方案3】:

为了看到您编写的代码实际上失败了,您需要做的不仅仅是编译没有任何额外标志的代码。如果您使用clang MemorySantizer 并禁用编译器优化,您将看到使用f[0] 完成的内存访问存在问题。

C 不做边界检查并将其留给程序员。 vector() 函数返回一个只能通过v[nl] ... v[nh] 寻址的内存位置

a.c. 中的第 23 行是 f[0]=1;

% clang -fsanitize=memory -fno-omit-frame-pointer -g -O0 a.c
% ./a.out 
Hello world

MemorySanitizer:DEADLYSIGNAL
==26321==ERROR: MemorySanitizer: SEGV on unknown address 0x600ffffffff8 (pc 0x0000010a9670 bp 0x7fffffffeaa0 sp 0x7fffffffea10 T101363)
==26321==The signal is caused by a WRITE memory access.
    #0 0x10a966f in main /home/fnoyanisi/a.c:23:9
    #1 0x1060b5a in __sanitizer::ReportDeadlySignalImpl(__sanitizer::SignalContext const&, unsigned int, void (*)(__sanitizer::SignalContext const&, void const*, __sanitizer::BufferedStackTrace*), void const*) /usr/src/contrib/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cc:211:3
    #2 0x1060b5a in __sanitizer::ReportDeadlySignal(__sanitizer::SignalContext const&, unsigned int, void (*)(__sanitizer::SignalContext const&, void const*, __sanitizer::BufferedStackTrace*), void const*) /usr/src/contrib/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cc:225
    #3 0x1060e8e in __sanitizer::HandleDeadlySignal(void*, void*, unsigned int, void (*)(__sanitizer::SignalContext const&, void const*, __sanitizer::BufferedStackTrace*), void const*) /usr/src/contrib/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cc:234:3

MemorySanitizer can not provide additional info.
SUMMARY: MemorySanitizer: SEGV /home/fnoyanisi/a.c:23:9 in main
==26321==ABORTING
% sed '23q;d' a.c
    f[0]=1;
% clang a.c
% ./a.out 
Hello world

5.000000
% 

【讨论】:

  • f[1] 和 f[2] 和 f[7] 呢?我怎样才能让 gcc 向我显示警告。我不太明白这个 % 实际上是什么?
  • % 是我的提示符(tcsh),示例来自 FreeBSD 机器(出于某种原因,clang MemorySanitizer 在 macOS 中被破坏)。我不确定 gcc 是否有 MemorySanitizer 之类的工具,但如果您使用的是 gnu/Linux,您可以尝试使用 -O0 编译和使用 Valgrind 吗?
  • 为什么不显示 f2 和 f7 的错误?这是什么叮当声?
  • 你能解释一下你的错误到底是什么意思吗?
  • 它不会显示f[2]f[7] 的错误,因为当尝试写入f[0] 指向的内存时,程序已经退出......如果你在问@987654334 @ 并发布一个 C 编程问题...这就像骑车说什么是梅赛德斯...en.wikipedia.org/wiki/Clang
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-02
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
相关资源
最近更新 更多