是%p说明符只用于有效的指针吗? [英] Is %p specifier only for valid pointers?

查看:111
本文介绍了是%p说明符只用于有效的指针吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我的平台 sizeof(int)== sizeof(void *),我有这个代码:

  printf(%p,rand()); 

这是未定义的行为,因为传递的值不是有效的指针, c $ c>%p ?

解决方案

要扩展@ larsman的答案你违反了一个约束,行为是未定义的),这里是一个实际的C实现其中 sizeof(int)== sizeof(void *),但是代码不等同于 printf(%p,(void *)rand());



摩托罗拉68000处理器有16个寄存器它们用于一般计算,但它们不是等价的。其中八个(名为 a0 a7 )用于访问存储器(地址寄存器) code> d0 到 d7 )用于算术(数据寄存器)。此架构的有效调用约定为


  1. d0中的前两个整数参数传递 d1 ;

  2. 传递 a0 a1 ;

  3. 将所有其他类型传递到堆栈,而不管大小。




  4. 这是一个完全合法的调用约定,类似于很多现代处理器使用的调用约定。



    例如,调用函数 void foo(int i,void * p),你会传递 i in d0 p



    请注意, function void bar(void * p,int i),你也会传递中的 i d0 p a0



    在这些规则下, printf(%p,rand())会传递 a0 d0 中的随机数参数。另一方面, printf(%p,(void *)rand())会传递 a0 a1 中的随机指针参数。



    va_list 结构将如下所示:

      struct va_list {
    int d0;
    int d1;
    int a0;
    int a1;
    char * stackParameters;
    int intsUsed;
    int pointersUsed;
    };

    前四个成员用寄存器的相应条目值初始化。 stackParameters 指向通过 ... 传递的第一个基于堆栈的参数, intsUsed pointersUsed 分别被初始化为命名参数的数量,这些参数是整数和指针。



    va_arg 宏是一个编译器固有的,它根据预期的参数类型生成不同的代码。




    • 如果参数类型是一个指针,则 va_arg(ap,T)会扩展为(T *)get_pointer_arg & ap)

    • 如果参数类型是整数,则 va_arg(ap,T)展开为(T)get_integer_arg(& ap)

    • 如果参数类型是别的, c> va_arg(ap,T)展开为 *(T *)get_other_arg(& ap,sizeof(T))。 >


    get_pointer_arg 函数如下所示:

      void * get_pointer_arg(va_list * ap)
    {
    void * p;
    switch(ap-> pointersUsed ++){
    case 0:p = ap-> a0;打破;
    情况1:p = ap-> a1;打破;
    case 2:p = *(void **)get_other_arg(ap,sizeof(p));打破;
    }
    return p;
    }

    get_integer_arg 函数如下:

      int get_integer_arg(va_list * ap)
    {
    int i;
    switch(ap-> intsUsed ++){
    case 0:i = ap-> d0;打破;
    情况1:i = ap-> d1;打破;
    case 2:i = *(int *)get_other_arg(ap,sizeof(i));打破;
    }
    返回i;
    }

    get_other_arg 函数如下:

      void * get_other_arg(va_list * ap,size_t size)
    {
    void * p = ap-> stackParameters;
    ap-> stackParameters + =((size + 3)&〜3);
    return p;
    }

    如前所述,调用 printf ,rand())将传递 a0 中的格式字符串和 d0 。但是当 printf 函数执行时,它会看到%p 格式并执行 va_arg (),并从 a1 中读取参数,使用(ap,void *)而不是 d0 。由于 a1 未初始化,它包含垃圾。



    进一步举例,如果你有 printf(%p%i%s,rand ),0,hello); 这将被调用如下:




    • a0 =格式字符串的地址(第一个指针参数)

    • a1 =字符串hello(第二个指针参数)

    • d0 =随机数参数)

    • d1 = 0(第二个整数参数)



    当执行 printf 函数时,它会按预期从 a0 读取格式字符串。当它看到%p 它会从 a1 检索指针并打印它,所以你得到的地址string hello。然后它将看到%i 并从 d0 中检索参数,因此它将打印一个随机数。最后,它看到%s 并从堆栈中检索参数。但你没有传递任何参数在堆栈!这将读取未定义的堆栈垃圾,这将很可能会崩溃你的程序,当它试图打印它,如果它是一个字符串指针。


    Suppose on my platform sizeof(int)==sizeof(void*) and I have this code:

    printf( "%p", rand() );
    

    Will this be undefined behavior because of passing a value that is not a valid pointer in place of %p?

    解决方案

    To expand upon @larsman's answer (which says that since you violated a constraint, the behavior is undefined), here's an actual C implementation where sizeof(int) == sizeof(void*), yet the code is not equivalent to printf( "%p", (void*)rand() );

    The Motorola 68000 processor has 16 registers which are used for general computation, but they are not equivalent. Eight of them (named a0 through a7) are used for accessing memory (address registers) and the other eight (d0 through d7) are used for arithmetic (data registers). A valid calling convention for this architecture would be

    1. Pass the first two integer parameters in d0 and d1; pass the rest on the stack.
    2. Pass the first two pointer parameters in a0 and a1; pass the rest on the stack.
    3. Pass all other types on the stack, regardless of size.
    4. Parameters passed on the stack are pushed right-to-left regardless of type.
    5. Stack-based parameters are aligned on 4-byte boundaries.

    This is a perfectly legal calling convention, similar to calling conventions used by many modern processors.

    For example, to call the function void foo(int i, void *p), you would pass i in d0 and p in a0.

    Note that to call the function void bar(void *p, int i), you would also pass i in d0 and p in a0.

    Under these rules, printf("%p", rand()) would pass the format string in a0 and the random number parameter in d0. On the other hand, printf("%p", (void*)rand()) would pass the format string in a0 and the random pointer parameter in a1.

    The va_list structure would look like this:

    struct va_list {
        int d0;
        int d1;
        int a0;
        int a1;
        char *stackParameters;
        int intsUsed;
        int pointersUsed;
    };
    

    The first four members are initialized with the corresponding entry values of the registers. The stackParameters points to the first stack-based parameters passed via the ..., and the intsUsed and pointersUsed are initialized to the number of named parameters which are integers and pointers, respectively.

    The va_arg macro is a compiler intrinsic which generates different code based on the expected parameter type.

    • If the parameter type is a pointer, then va_arg(ap, T) expands to (T*)get_pointer_arg(&ap).
    • If the parameter type is an integer, then va_arg(ap, T) expands to (T)get_integer_arg(&ap).
    • If the parameter type is something else, then va_arg(ap, T) expands to *(T*)get_other_arg(&ap, sizeof(T)).

    The get_pointer_arg function goes like this:

    void *get_pointer_arg(va_list *ap)
    {
        void *p;
        switch (ap->pointersUsed++) {
        case 0: p = ap->a0; break;
        case 1: p = ap->a1; break;
        case 2: p = *(void**)get_other_arg(ap, sizeof(p)); break;
        }
        return p;
    }
    

    The get_integer_arg function goes like this:

    int get_integer_arg(va_list *ap)
    {
        int i;
        switch (ap->intsUsed++) {
        case 0: i = ap->d0; break;
        case 1: i = ap->d1; break;
        case 2: i = *(int*)get_other_arg(ap, sizeof(i)); break;
        }
        return i;
    }
    

    And the get_other_arg function goes like this:

    void *get_other_arg(va_list *ap, size_t size)
    {
        void *p = ap->stackParameters;
        ap->stackParameters += ((size + 3) & ~3);
        return p;
    }
    

    As noted earlier, calling printf("%p", rand()) would pass the format string in a0 and the random integer in d0. But when the printf function executes, it will see the %p format and perform a va_arg(ap, void*), which will use get_pointer_arg and read the parameter from a1 instead of d0. Since a1 was not initialized, it contains garbage. The random number you generated is ignored.

    Taking the example further, if you had printf("%p %i %s", rand(), 0, "hello"); this would be called as follows:

    • a0 = address of format string (first pointer parameter)
    • a1 = address of string "hello" (second pointer parameter)
    • d0 = random number (first integer parameter)
    • d1 = 0 (second integer parameter)

    When the printf function executes, it reads the format string from a0 as expected. When it sees the %p it will retrieve the pointer from a1 and print it, so you get the address of the string "hello". Then it will see the %i and retrieve the parameter from d0, so it prints a random number. Finally, it sees the %s and retrieves the parameter from the stack. But you didn't pass any parameters on the stack! This will read undefined stack garbage, which will most likely crash your program when it tries to print it as if it were a string pointer.

    这篇关于是%p说明符只用于有效的指针吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆