user1559625
user1559625

Reputation: 2673

Set integer value at unaligned address?

I had a short interview where a question is like this: set an integer value to be 0xaa55 at address 0x*****9.

The only thing I noticed is that the address given is not aligned on word boundary. So setting an int *p to the address should not work. Then is it just using a unsigned char *p to assign the value byte-wise? Is it the point of this interview question? There is no point of doing this in real life, is there?

Upvotes: 2

Views: 1758

Answers (3)

kkrambo
kkrambo

Reputation: 7057

Yes, you may need to deal with unaligned multi-byte values in real life. Imagine your device exchanges data with another device. For example, this data may be a message structure sent over a network or a file structure saved to disk. The format of that data may be predefined and not under your control. And the definiton of the data structure may not account for alignement (or even endianness) restrictions of your device. In these situations you'll need to take care when accessing these unaligned multi-byte values.

Upvotes: 0

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215221

memcpy((void *)0x23456789, &(int){0xaa55}, sizeof(int));

Upvotes: 3

Jonathan Leffler
Jonathan Leffler

Reputation: 753675

You need to get back to the interviewer with a number of subsidiary questions:

  1. What is the size in bytes of an int?
  2. Is the machine little-endian or big-endian?
  3. Does the machine handle non-aligned access automatically?
  4. What is the performance penalty for handling non-aligned access automatically?
  5. What is the point of this?

The chances are that someone is thinking of marshalling data the quick and dirty way.

You're right that one basic process is to write the bytes via a char * or unsigned char * that is initialized to the relevant address. The answers to my subsidiary questions 1 and 2 determine the exact mechanism to use, but for a 2-byte int in little-endian format, you might use:

unsigned char *p = 0x*****9; // Copied from question!
unsigned int v = 0xAA55;

*p++ = v & 0xFF;
v >>= 8;
*p   = v & 0xFF;

You can generalize to 4-byte or 8-byte integers easily; handling big-endian integers is a bit more fiddly.


I assembled some timing code to see what the relative costs were. Tested on a MacBook Pro (2.3 GHz Intel Core i7, 16 GiB 1333 MHz DDR3 RAM, Mac OS X 10.7.5, home-built GCC 4.7.1), I got the following times for the non-optimized code:

Aligned:          0.238420
Marshalled:       0.931727
Unaligned:        0.243081
Memcopy:          1.047383
Aligned:          0.239070
Marshalled:       0.931718
Unaligned:        0.242505
Memcopy:          1.060336
Aligned:          0.239915
Marshalled:       0.934913
Unaligned:        0.242374
Memcopy:          1.049218

When compiled with optimization, I got segmentation faults, even without -DUSE_UNALIGNED — which puzzles me a bit. Debugging was not easy; there seemed to be a lot of aggressive inline optimization which meant that variables could not be printed by the debugger.

The code is below. The Clock type and the time.h header (and timer.c source) are not shown, but can be provided on request (see my profile). They provide high resolution timing across most platforms (Windows is shakiest).

#include <string.h>
#include <stdio.h>
#include "timer.h"

static int array[100000];
enum { ARRAY_SIZE = sizeof(array) / sizeof(array[0]) };
static int repcount = 1000;

static void uac_aligned(int value)
{
    int *base = array;
    for (int i = 0; i < repcount; i++)
    {
        for (int j = 0; j < ARRAY_SIZE - 2; j++)
            base[j] = value;
    }
}

static void uac_marshalled(int value)
{
    for (int i = 0; i < repcount; i++)
    {
        char *base = (char *)array + 1;
        for (int j = 0; j < ARRAY_SIZE - 2; j++)
        {
            *base++ = value & 0xFF;
            value >>= 8;
            *base++ = value & 0xFF;
            value >>= 8;
            *base++ = value & 0xFF;
            value >>= 8;
            *base   = value & 0xFF;
            value >>= 8;
        }
    }
}

#ifdef USE_UNALIGNED
static void uac_unaligned(int value)
{
    int *base = (int *)((char *)array + 1);
    for (int i = 0; i < repcount; i++)
    {
        for (int j = 0; j < ARRAY_SIZE - 2; j++)
            base[j] = value;
    }
}
#endif /* USE_UNALIGNED */

static void uac_memcpy(int value)
{
    for (int i = 0; i < repcount; i++)
    {
        char *base = (char *)array + 1;
        for (int j = 0; j < ARRAY_SIZE - 2; j++)
        {
            memcpy(base, &value, sizeof(int));
            base += sizeof(int);
        }
    }
}

static void time_it(int value, const char *tag, void (*function)(int value))
{
    Clock c;
    char buffer[32];
    clk_init(&c);
    clk_start(&c);
    (*function)(value);
    clk_stop(&c);
    printf("%-12s  %12s\n", tag, clk_elapsed_us(&c, buffer, sizeof(buffer)));
}

int main(void)
{
    int value = 0xAA55;

    for (int i = 0; i < 3; i++)
    {
        time_it(value, "Aligned:",   uac_aligned);
        time_it(value, "Marshalled:", uac_marshalled);
#ifdef USE_UNALIGNED
        time_it(value, "Unaligned:", uac_unaligned);
#endif /* USE_UNALIGNED */
        time_it(value, "Memcopy:",   uac_memcpy);
    }
    return(0);
}

Upvotes: 7

Related Questions