为什么位操作的返回值每次都会改变?

时间:2020-05-19 05:02:33

标签: c

我是计算机科学的初学者。我了解到,指针是一种复合数据类型,它指示数据的地址以及数据的类型和大小。指针的类型转换只会更改读取大小,但会更改起始地址。为了证实这一点,我做了一个实验。

请参见下面的代码。我更改了变量“样本”的指针类型,但我认为它仍指向样本的第一个字节。大小没有改变。然后,我使(字符类型)指针向左跳一个字节(在代码中为“ p = p-1”)。之后,我将其转换回短类型。我认为指向的数据是0x..24(..表示数据在0x2456前面)。最后,我使用位操作“ <<”更改为0x2400。但是,每次运行时,我都会得到随机数。

   #include<stdio.h>

   int main(void){
       short sample = 0x2456;

       char *p = (char*) &sample; 
       p = p-1;
       printf("%d\n",*((short*)p)<<8 );
       return 0;
   }

4 个答案:

答案 0 :(得分:1)

出于网络安全原因,大多数operating systems都提供address space layout randomization,其中包括call stack调用的main函数的crt0

我不知道为什么无法获得固定值。

ASLR可能解释了为什么在OS上多次运行程序会产生不同的输出。您的p 可能指向调用堆栈的某个奇怪位置。

当然,请阅读有关undefined behavior的更多信息,以及Modern C以及C11的规范,即n1570

如果您使用最新的GCC来编译C代码foo.c,请考虑使用gcc -Wall -Wextra -O -S foo.c对其进行编译,然后查看发出的汇编代码foo.s。然后,您将了解将什么值传递给printf。它是特定于实现的。

答案 1 :(得分:1)

存在C语言,其语言可以保证,然后有未定义的代码,不能保证任何特定行为。

C语言不允许允许所有形式的通配符转换和任意指针算术-如果违反规则,则可能发生任何事情,并且不能保证确定性的结果。如果要分析此类不确定性代码,则可能会得到任何结果,包括程序崩溃。

要详细分析代码并显示错误之处:

  • char *p = (char*) &sample;这是有效的转换。 C中有一些特殊的规则,允许通过诸如此类的指针转换将任何类型转换为字符类型。进行这种转换后,您可以取消引用指针。
  • 然而char特别具有实现定义的签名。这意味着它可以等于signed charunsigned char,具体取决于编译器。如果已签名,那么如果取消引用该指针,则可能会遇到意外的输出。
  • p = p-1;未定义的行为,任何事情都可能发生。我们只能对指向数组的指针进行指针算术运算。为了确定指针算术是否有效,将指向单个值变量(“标量”)的指针视为指向具有1个项的数组的指针。

    指针算术必须导致指针指向已分配数组中从索引0n+1的数组项,其中n是数组大小。 C允许将1项指向数组之外的一种特殊情况。但是,它不允许指针算术像在代码中那样导致指针指向数组之前的项。

    (作为一种特殊情况,为了分析原始数据,我们可以使用字符指针对任何数据类型进行增量迭代。然后将数据视为sizeof(data)个字符的数组。但是您的代码无法执行此操作。)

  • *(short*)p也是未定义的行为,因为编译器不知道p现在指向何处或在那里存储什么类型。在所有需要 aligned access 的系统上,这也是未定义的行为,因为指针现在绝对是未对齐的。

    最后,您现在访问的内存区域可能会被系统阻止,从而导致访问冲突或分段错误。这种错误的性质和结果超出了C的范围。

  • 尽管上述所有问题,如果您的程序仍设法吐出一些二进制结果,则该结果可能会受到CPU endianess 的影响。在第一行char *p = (char*) &sample;p可以指向MS字节或LS字节,具体取决于系统。

  • 如果前一句话中未定义的二进制粘滞符恰好包含MSB集,则可以通过左移负整数再次调用未定义的行为。

摘要:我计算了4种未定义行为的情况和2种实现定义行为的情况。此代码没有预期的保证或确定性结果。检查结果没有什么可学的。您只能通过分析给出此类结果的代码为什么错误来学习一些东西。

答案 2 :(得分:0)

根据您的代码,我假设您想将0x24移到较高(实际上是较低)的字节上。尝试p=p+1,看看是否获得理想的结果。

如果您执行(int)*p << 8,则每次读取2400都比读取指针短。

或者您可以做一些疯狂的事情,例如在初始化指针之后初始化一些变量,这样当我们移动指针时,它不会得到垃圾,而是变量的一部分

#include<stdio.h>

   int main(void){
       short sample = 0x2456;

       char *p = (char*) &sample; 
       int zero = 0;
       p = p+1;
       printf("%x\n",*((short*)p) << 8 ); 
       return 0;
   }

您甚至可以像这样打印0x1224(两者的一半)

   #include<stdio.h>

   int main(void){
       short sample = 0x2456;

       char *p = (char*) &sample; 
       int zero = 0x12;
       p = p+1;
       printf("%x\n",*((short*)p);    //will print 1224
       return 0;
   }

边注:我假设两个变量都在单个堆栈帧中,并且假定内存为低端字节序。结果可能会因编译器和目标系统而异。

答案 3 :(得分:-1)

我只是要在黑暗中拍照。如果要打印存储在样本的前8位中的值(在这种情况下为1)。注意,这假设内存是小端的:

short sample = 1;
char* p = (char*)&sample;
p = p - 1;
std::cout << "the first 8 bits of sample: " << (*(short*)p >> 8) << std::endl;