0

I am trying to solve the following problem:

/*
 * Return 1 if ptr1 and ptr2 are within the *same* 64-byte aligned
 * block (or word) of memory. Return zero otherwise.
 *
 * Operators / and % and loops are NOT allowed.
 */
/*

I have the following code:

int withinSameBlock(int * ptr1, int * ptr2) {
  // TODO
  int temp = (1 << 31) >> 25;
  int a = ptr1;
  int b = ptr2;
  return (a & temp) == (b & temp);
}

I have been told that this correctly solves the problem, but I am unsure how it works. Specifically, how does the line int temp = (1 << 31) >> 25; help to solve the problem?

JBentley
  • 6,099
  • 5
  • 37
  • 72
LittleDriver
  • 107
  • 2
  • 8
  • 1
    Please make some effort to come up with a meaningful title for your question. The title should be directly related to the question and should enable someone viewing it in a list to grasp roughly what the question will be about. Also, unless the question is genuinely about both C++ and C, then you should decide what language you're referring to, and stick to that tag. – JBentley Jul 19 '14 at 04:21
  • i hope you can give me a answer about this question, ok? – LittleDriver Jul 19 '14 at 04:27
  • 4
    The question needs to be rewritten – Ed Heal Jul 19 '14 at 04:28
  • this code is answer, i submit it successful – LittleDriver Jul 19 '14 at 04:28
  • What exactly is your question? Are you asking what the bitwise shift operators do? – JBentley Jul 19 '14 at 04:31
  • i don't know how this code can solve that problem.especially, int temp = (1 << 31) >> 25; – LittleDriver Jul 19 '14 at 04:33
  • the code is answer, but i can't understood it. if you can have a new answer can solve question, please tell me – LittleDriver Jul 19 '14 at 04:35
  • Ok, I edited your question so that it is clearer. – JBentley Jul 19 '14 at 04:37
  • @user3787025@JBentley first, i will say sorry. i can't understood ‘64-byte’'s meanning. so i don't know why use 'temp = (1 << 31) >> 25' and & to a and b – LittleDriver Jul 19 '14 at 04:46
  • @user3787025 You should wait a while, until everyone has had time to post an answer before choosing one. – Michael Dorst Jul 19 '14 at 05:29
  • @anthropomorphic i get my need answer – LittleDriver Jul 19 '14 at 05:30
  • @user3787025 Maybe so, but that doesn't mean someone else won't give another answer that's even better than the one you've chosen. You should wait a little while (maybe an hour or so), and then read them all and pick the one that makes the most sense, and is the most informative. – Michael Dorst Jul 19 '14 at 05:32
  • It's your duty to the other users of this site to pick the best answer, because you're the only one who can, and because the one you pick will be moved to the top of the page, making other, possibly better answers harder to find. – Michael Dorst Jul 19 '14 at 05:35
  • but i really find my need and right answer, Don't I should choose it? i just find my need answer – LittleDriver Jul 19 '14 at 05:41
  • @anthropomorphic sorry, i shouldn't argue with this problem. i come there little time, so i can't know the rule in there, i will correct it next time, thanks – LittleDriver Jul 19 '14 at 05:46

4 Answers4

4

The line:

int temp = (1 << 31) >> 25;

is either incorrect or triggers undefined behavior (depending on wordsize). It just so happens that the undefined behavior on your machine and your compiler does the right thing and just happens to give the correct answer. To avoid undefined behavior and make the code clearer, you should use:

int withinSameBlock(int * ptr1, int * ptr2) {
    uintptr_t temp = ~(uintptr_t)63;
    uintptr_t a = (uintptr_t)ptr1;
    uintptr_t b = (uintptr_t)ptr2;
    return (a & temp) == (b & temp);
}
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • How does `int temp = (1 << 31) >> 25;` trigger undefined behavior? – Michael Dorst Jul 19 '14 at 05:13
  • it is UD because it generate different result depends on how big an int is. – Non-maskable Interrupt Jul 19 '14 at 05:21
  • Oh I see. If the `int` is 32 bit, then `(1 << 31) >> 25` will result in `0xffffffc0`, but if it's 64 bit then it will be `0x00000040`. – Michael Dorst Jul 19 '14 at 05:21
  • @Chris Dodd you should explain why `(1 << 31) >> 25` is UD and how `~(uintptr_t)63` obtains the same result. – Michael Dorst Jul 19 '14 at 05:24
  • `1 << 31` is UB because the C standard says it is UB. See section 6.5.7/4 of C99 (to get a free copy, search for n1256.pdf). If you ask "Why does the C standard say it is UB", the answer is that C is supported on a wide range of systems, and there are some systems which do not support wraparound behaviour on integer overflow (e.g. shifting into the sign bit). – M.M Jul 19 '14 at 05:26
  • `(1 << 31)` is undefined behavior on a 32-bit machine as it overflows a signed int (section 6.5.7.4). As a result, it might give any result, or might trap or otherwise crash. `(uintptr_t)63` takes the constant 63 and converts it to an unsigned integer the same size as a pointer. The `~` operator then flips all bits, leaving just the least significant 6 bits clear. – Chris Dodd Jul 19 '14 at 07:14
3

I'm not sure where you get that code (homework?) but this is terrible. 1. casting pointer to int and do arithmetics is generally very bad practice. The actual size is undefined by those primitive types, for instant, it breaks on every architecture that pointer or int is not 32-bit.

You should use uintptr_t, which is generally larger than or equal to the size of a pointer (except for theoretical arch permitted by ambigous spec)

For example:

#include <stdint.h>
#include <stdio.h>

int withinSameBlock(int * ptr1, int * ptr2) {
  uintptr_t p1 = reinterpret_cast<uintptr_t>(ptr1);
  uintptr_t p2 = reinterpret_cast<uintptr_t>(ptr2);
  uintptr_t mask = ~ (uintptr_t)0x3F;
  return (p1 & mask) == (p2 & mask);
}


int main() {
  int* a = (int*) 0xdeadbeef;
  int* b = (int*) 0xdeadbeee;
  int* c = (int*) 0xdeadc0de;
  printf ("%p, %p: %d\n", a, b, withinSameBlock(a, b));
  printf ("%p, %p: %d\n", a, c, withinSameBlock(a, c));
  return 0;
}
Non-maskable Interrupt
  • 3,841
  • 1
  • 19
  • 26
  • "except for theoretical arch permitted by ambigous spec" Say what? – Michael Dorst Jul 19 '14 at 05:27
  • The spec only require "Integer type capable of holding a value converted from a void pointer and then be converted back to that type with a value that compares equal to the original pointer." So if void* is 32-bit, but the system only use 24-bit for addressing, it's legal for uintptr_t to be just 24-bit in size. Ref: http://stackoverflow.com/questions/1845482/what-is-uintptr-t-data-type – Non-maskable Interrupt Jul 19 '14 at 05:32
  • It's not *guaranteed* that this version will work either (even if uintptr_t exists) but it's as close as you can get in portable code. – M.M Jul 19 '14 at 05:36
  • @user3787025 You don't need to apologize, it's nothing wrong with learning and doing homework. We just point out potential issue that might bite you later, although it might be out of your current homework's scope. – Non-maskable Interrupt Jul 19 '14 at 05:51
  • yeah, i know, but like"uintptr_t", we don't learn in my study, so i don't use it. Now, i see your answer, i will correct – LittleDriver Jul 19 '14 at 05:59
  • i want to ask you. your mean is that my code is not work in 64bit computer? because the pointer size is not same in different computer. – LittleDriver Jul 19 '14 at 06:09
  • uintptr_t might be the same size as a void*. It might be larger. It could conceivably be smaller, although such a C++ implementation approaches perverse. For example on some hypothetical platform where void* is 32 bits, but only 24 bits of virtual address space are used, you could have a 24-bit uintptr_t which satisfies the requirement – LittleDriver Jul 19 '14 at 06:14
  • @user3787025 Yes, your code { int temp = (1 << 31) >> 25; } do not do what you intended on 64-bit system, and likely many other systems. Also don't worry too much on the hypothetical platform, it is very insane to implement such compiler just to break people's code. – Non-maskable Interrupt Jul 19 '14 at 06:18
2

First, we need to be clear that the code will only work on systems where a pointer is 32 bits, and int is also 32 bits. On a 64-bit system, the code will fail miserably.

The left shift (1 << 31) sets the most significant bit of the int. In other words, the line

int temp = (1 << 31);

is the same as

int temp = 0x80000000;

Since an int is a signed number, the most significant bit is the sign bit. Shifting as signed number to the right copies the sign bit into lower order bits. So shifting to the right 25 times results in a value that has a 1 in the upper 26 bits. In other words, the line

int temp = (1 << 31) >> 25;

is the same as (and would be much clearer if it was written as)

int temp = 0xffffffc0;

The line

return (a & temp) == (b & temp);

compares the upper 26 bits of a and b, ignoring the lower 6 bits. If the upper bits match, then a and b point to the same block of memory.

user3386109
  • 34,287
  • 7
  • 49
  • 68
  • thanks, but i want ask, why we compares the upper 26 bits of a and b?compare this can determine ptr1 and ptr2 are within the *same* 64-byte aligned block (or word) of memory? – LittleDriver Jul 19 '14 at 05:02
  • The comparison ignores the lower 6 bits of `a` and `b`. The lower 6 bits of the pointer are the address of a byte within the 64-byte block. The upper 26 bits are the block address. So the code is checking if the block address is the same. – user3386109 Jul 19 '14 at 05:09
  • yeah, your answer is my need, thanks.i get it, upper 26 bits are the block address, and lower bits are the offset in block. the question let us determine if ptr1 and ptr2 are in same block ? right? – LittleDriver Jul 19 '14 at 05:29
  • be aware that whether or not `1 << 31` works (and if so, whether it's the same as `(int)0x80000000`) , and whether `int temp = 0xffffffc0;` works, depends on your CPU type (and maybe other things also). The other answers discuss this in more detail and provide alternatives that work on all 32bit systems. – M.M Jul 19 '14 at 05:41
  • @Matt McNabb thanks your advice. i already make sure it can works, thanks – LittleDriver Jul 19 '14 at 05:44
1

Assuming 32 bit pointers, if the two pointers are in the same 64-byte block of memory, then their addresses will vary only in the 6 least significant bits.

(1 << 31) >> 25 will give you a bitmask that looks like this:

11111111111111111111111111000000

a=ptr1 and b=ptr2 will set a and b equal to the value of the pointers, which are memory addresses. The bitwise AND of temp with each of these (i.e., a&temp and b&temp) will mask off the last 6 bits of the addresses held by a and b. If the remaining 26 bits are the same, then the original addresses must have been within 64 bytes of each other.

Demo code:

#include <stdio.h>

void main()
{
    int temp = (1 << 31) >> 25;
    printf("temp=%x\n",temp);
    int p=5, q=6;
    int *ptr1=&p, *ptr2=&q;
    printf("*ptr1=%x, *ptr2=%x\n",ptr1, ptr2);
    int a = ptr1;
    int b = ptr2;
    printf("a=%x, b=%x\n",a,b);
    if ((a & temp) == (b & temp)) printf("true\n");
    else printf("false\n");
}
Paul Ratazzi
  • 6,289
  • 3
  • 38
  • 50
  • I have added a small C program to my answer. Assuming 32 bits, it will show that temp is `0xffffffc0`. That is the same as the binary mask I have shown above. – Paul Ratazzi Jul 19 '14 at 05:12
  • your question is right, but i only chose one answer. but you also help me , thanks – LittleDriver Jul 19 '14 at 05:32