虽然(* p ++)危险吗?

时间:2014-10-03 15:18:15

标签: c arrays pointers

使用while(*p++)检查数组是否有更多元素危险?

如果在下一个内存位置有一些值并且该值不是数组的一部分,可能会出现问题。

这个简单的代码:

#include <stdio.h>

void f(float *p) {
    printf("%p : %.f\n", p, *p);
    while(*p++){
        printf("%p : %.f\n", p, *p);
    }
    printf("%p : %.f\n", p, *p);
}

int main() {
    float p[] = {2.2, 3.2};
    f(p);
    return 0;
}

给出了这个输出:

0x7fffed4e8f10 : 2
0x7fffed4e8f14 : 3
0x7fffed4e8f18 : 0
0x7fffed4e8f1c : 0

那么,如果在0x7fffed4e8f18上,该值为≠0会使我的代码错误吗?

6 个答案:

答案 0 :(得分:8)

只要p超出数组边界,解除引用就会调用未定义的行为。所以是的,这样做很危险。

答案 1 :(得分:3)

已经有很多答案可以解释通过读取数组的边界而产生的未定义行为。但是还有一个(潜在的)问题还没有人提到过。

如果您的数组可以包含0作为有效值,那么您将假设错误的大小。也就是说,

void
f(float * p)
{
  do
    {
      printf("% .01f\n", *p);
    }
  while(*p++);
}

int
main()
{
  float p[] = {-1.0f, -0.5f, 0.0f, 0.5f, 1.0f};
  f(p);
  return 0;
}

将输出

-1.00
-0.50
 0.00

并“忽视”其余元素。虽然此特定示例未调用未定义的行为,但它可能不是您所期望的。

我猜你已经通过字符串编程来实现这种模式。按照惯例,我们需要一个字符数组来表示

的文本字符串
  1. 不包含任何NUL字节和
  2. 通过在最后一个字节之后放置一个NUL字节来终止。
  3. 如果满足两个要求,请执行

    void
    roll_all_over_it(char * string)
    {
      while(*string);
        {
          putchar(*string++);
        }
    }
    
    合同是安全的。

    这使得使用字符串更加方便,因为我们不必在每个字符串旁边保持一个整数来跟踪其长度。另一方面,它已经使很多(粗心编写的)程序易受缓冲区溢出攻击,并且还存在其他问题。例如,请参阅GNU libc manual中对fgets的讨论:

      

    警告:如果输入数据的字符为空,则无法说明。因此,除非您知道数据不能包含空值,否则请勿使用fgets。不要使用它来读取用户编辑的文件,因为如果用户插入空字符,您应该正确处理它或打印清晰的错误消息。我们建议您使用getline代替fgets

答案 2 :(得分:2)

是的,它是:你的函数接受一个指针参数,但你没有检查以确保它不是一个NULL指针。不允许取消引用空指针 正如其他人所指出的那样:取消引用无效指针(超出范围)会导致未定义的bahviour,这很糟糕。

此外,您使用正确的格式字符串来打印指针(%p),但使用-Wall -pedantic编译代码。打印指针值是少数 指向void *的指针的情况之一。 IE改变:

printf("%p : %.f\n", p, *p);

printf("%p : %.f\n", (void *) p, *p);

更新
回应你的评论:看起来你实际上是在试图确定作为参数传递的数组的长度。事情的简单事实是,你不能。数组衰减为指针。指针不是数组,因此无法确定原始数组的长度。至少:不可靠。
如果你正在处理一个数组,并且你想知道它的长度:让函数的调用者传递长度作为参数:

void f(float *arr, size_t arr_len)
{
    if (arr == NULL)
        exit( EXIT_FAILURE );//handle error
    //do stuff
}

简而言之,您的函数在很大程度上依赖于数组之后的0。函数本身也可以通过NULL调用,解除引用null也是非法的。所以,是的,您的代码很危险。

//example setup
float foo = 123.4f
float *bar = malloc(123 * sizeof *foo);//<-- uninitialized memory, contains junk
//omitting if (bar == NULL) check, so bar might be null
//dangerous calls:
f(&foo);
f(bar);//<-- bar could be null if malloc failed, and contains junk if it didn't dangerous
f(NULL);

答案 3 :(得分:2)

在C语言中,只有一种方法可以确定数组中有多少个元素:它是从原始数组类型中查找该值,即数组定义中使用的那个。任何其他尝试通过一些发明的运行时机制来计算元素的数量都是无用的。

在您的特定示例中,函数f无法访问原始数组类型(因为您在将数组传递给f时将数组转换为指针)。这意味着无论你做什么,都无法在f内恢复数组的原始大小。

如果你坚持将数组作为f指针传递给float *,那么一个好主意是从调用者手动传递原始数组大小(即从main),作为函数f的额外参数。

“终止值”技术,您使用某种特殊元素值来指定数组的最后一个元素(例如,零)也可以用于此目的,但通常更糟糕的是只是传递大小从外部。无论如何,您必须确保手动将该终止值包含在数组中。它本身不会出现在那里。

答案 4 :(得分:1)

是的,如果数组中没有零元素,那么编写代码的方式会产生未定义的行为

也就是说,while(*p++)本身并不坏,但将它应用于未使用零元素显式终止的数组。 I. e。以下版本是安全的:

#include <stdio.h>

//p must point to a zero terminated array
void f(float *p) {
    printf("%p : %.f\n", p, *p);
    while(*p++){
        printf("%p : %.f\n", p, *p);
    }
    //printf("%p : %.f\n", p, *p);    //p points past the end of the array here, so dereferencing it is undefined behavior
}

int main() {
    float array[] = {2.2, 3.2, 0};    //add a terminating element
    f(array);
    return 0;
}

但是,我通常更喜欢显式传递数组的大小:

#include <stdio.h>

void f(int floatCount, float *p) {
    printf("%p : %.f\n", p, *p);
    for(int i = 0; i < floatCount; i++) {
        printf("%p : %.f\n", p+i, p[i]);
    }
}

int main() {
    float array[] = {2.2, 3.2};
    int count = sizeof(array)/sizeof(*array);
    f(count, array);
}

顺便说一下:数组不是指针,但是当它们传递给函数时它们会衰减成指针。我更改了命名以反映这一点。

答案 5 :(得分:1)

你所做的确实非常危险!

您无法控制数组的最后一个元素为null,并且取消引用将在数组的最后一个元素之后的指针。 Undefinit行为的两点!

这是你应该做的:

#include <stdio.h>

void f(float *p) {
    printf("%p : %.f\n", p, *p);
    while(*p++){
        printf("%p : %.f\n", p, *p);
    }
    /* because of ++, p is now one element to far : stop and do not print ... */
}

int main() {
    float p[] = {2.2, 3.2, 0}; /* force a 0 as a last element marker */
    f(p);
    return 0;
}