2015-08-07 2 views
2

Я не могу понять выход этой программы. То, что я получаю от этого, это то, что указатели p, q, r, s указывали на нуль.typecasting указатель на int.

Тогда было приведение типов. Но как черт, выход вышел как 1 4 4 8. Возможно, я ошибаюсь в своих мыслях. Поэтому, пожалуйста, поправьте меня, если я ошибаюсь.

int main() 
{ 

    int a, b, c, d; 
    char* p = (char*)0; 
    int *q = (int *)0; 
    float* r = (float*)0; 
    double* s = (double*)0; 

    a = (int)(p + 1); 
    b = (int)(q + 1); 
    c = (int)(r + 1); 
    d = (int)(s + 1); 

    printf("%d %d %d %d\n", a, b, c, d); 

    _getch(); 
    return 0; 
} 
+5

Полностью неопределенное поведение – CoryKramer

+5

@CoryKramer Хотя я согласен с тем, что это UB, я думаю, что вопрос связан скорее с фактической арифметикой указателя (добавление 1 к указателю int увеличит адрес по размеру int) –

+0

Связанный: http: //stackoverflow.com/questions/4772932 –

ответ

3

Арифметика указателя, в этом случае добавляющая целочисленное значение к значению указателя, увеличивает значение указателя в единицах типа, на который указывает. Если у вас есть указатель на 8-байтовый тип, добавление 1 к этому указателю будет продвигать указатель на 8 байтов.

Арифметика указателя действительна только в том случае, если как исходный указатель, так и результат добавления указывают на элементы одного и того же объекта массива или только за его конец.

Способ стандарт С описывает это (параграф 6.5.6):

Когда выражение, которое имеет целочисленный тип добавляется или вычитается из указателя, результат имеет тип операнд указателя.Если операнд указателя указывает на элемент объекта массива, а массив достаточно велик, результат указывает на смещение элемента из исходного элемента таким образом, что разница в нижних индексах элементов и элементов массива 0 равна целочисленное выражение.
[...]
Если оба указатель операнд и точка результата элементам же массив объектов, или один за последний элемент массива объектов, оценки не должен производить Чрезмерно поток; в противном случае поведение не определено. Если результат указывает один за последним элементом объекта массива , он не должен использоваться в качестве операнда унарного оператора, который оценивается.

Указатель, расположенный за концом массива, действителен, но вы не можете его разыменовать. Один объект без массива рассматривается как 1-элементный массив.

Ваша программа имеет неопределенное поведение. Вы добавляете 1 к нулевому указателю. Поскольку нулевой указатель не указывает на какой-либо объект, арифметика указателя на нем не определена.

Но компиляторы не обязаны обнаруживать неопределенное поведение, и ваша программа будет , вероятно, относиться к нулевому указателю точно так же, как к любому действительному значению указателя, и выполнять арифметику на нем одинаково. Таким образом, если нулевые указатель указывает на адрес 0 (это не гарантировала, кстати, но это очень часто), а затем добавить 1 к нему будет вероятно дать вам указатель для решения N, где N является размер в байтах типа, на который он указывает.

Вы затем преобразовать полученный указатель на int (который в лучшем случае определяется реализация, потеряет информацию, если указатели больше int, и могут дать ловушку представления) и распечатать значение int. Результат, на большинстве систем, вероятно, покажет вам размеры char, int, float и double, которые обычно равны 1, 4, 4 и 8 байтам соответственно.

Поведение вашей программы не определено, но то, как оно на самом деле ведет себя в вашей системе, типично и неудивительно.

Вот программа, которая не имеют неопределенное поведение, которое иллюстрирует ту же точку:

#include <stdio.h> 
int main(void) { 

    char c; 
    int i; 
    float f; 
    double d; 

    char *p = &c; 
    int *q = &i; 
    float *r = &f; 
    double *s = &d; 

    printf("char: %p --> %p\n", (void*)p, (void*)(p + 1)); 
    printf("int: %p --> %p\n", (void*)q, (void*)(q + 1)); 
    printf("float: %p --> %p\n", (void*)r, (void*)(r + 1)); 
    printf("double: %p --> %p\n", (void*)s, (void*)(s + 1)); 

    return 0; 
} 

и выход на моей системе:

char: 0x7fffa67dc84f --> 0x7fffa67dc850 
int: 0x7fffa67dc850 --> 0x7fffa67dc854 
float: 0x7fffa67dc854 --> 0x7fffa67dc858 
double: 0x7fffa67dc858 --> 0x7fffa67dc860 

Выход не ясно, как выход вашей программы, но если вы внимательно изучите результаты, вы увидите, что добавление 1 к char* продвигает его на 1 байт, int* или float* на 4 байта и double* - 8 байтов. (Кроме char, которые по определению имеют размер 1 байт, они могут различаться в некоторых системах.)

Обратите внимание, что выход формата "%p" определяется реализацией и может или не может отражать вид арифметических отношений, которые можно ожидать. Я работал над системами (векторными компьютерами Cray), где приращение указателя char* фактически обновляло бы смещение байтов, хранящееся в 3-х разрядах старшего разряда 64-битного слова. В такой системе выход моей программы (и вашей) будет намного сложнее интерпретировать, если вы не знаете детали низкого уровня работы машины и компилятора.

Но для большинства целей вам не нужно знать эти детали низкого уровня. Важно то, что арифметика указателя работает так, как описано в стандарте C. Знание того, как это делается на уровне бит, может быть полезно для отладки (это в значительной степени то, что для %p), но не нужно писать правильный код.

+0

В порядке, сэр, это то, что я получил. Поскольку все указатели указывали на NULL изначально, поэтому арифметика указателя в этом случае не была определена, теперь наш компилятор фактически не делает этого. Но когда мы добавляем 1, то он продвигает адрес по количеству байтов (в зависимости от типа переменной указателя). Теперь, преобразование этого адреса в int дает мне типичный размер символов, ints, floats из-за типов указателей, на которые они в основном ссылались. Вот почему я получаю этот вид вывода. Надеюсь, я понял, что вы сказали. –

+0

@NajmusSakib: Да, это по существу правильно. Одна небольшая формулировка: указатели не указывали на NULL, они * были * нулевыми указателями. Нулевой указатель не указывает ни на что. –

-1

Получаемый результат относится к размеру каждого из соответствующих типов. Когда вы выполняете арифметику указателей как таковую, она увеличивает значение указателя на добавленное значение, умноженное на размер базового типа. Это происходит для обеспечения правильного доступа к массиву.

Поскольку размер char, int, float и double являются 1, 4, 4 и 8 соответственно на вашей машине, те, которые отражаются при добавлении 1 к каждому из соответствующих указателей.

Edit:

Убрана альтернативный код, который я думал, что не проявляли неопределенное поведение, которое на самом деле сделал.

+0

большое спасибо! Я получил его :) –

+0

Нет проблем. Не стесняйтесь [принять этот ответ] (http://stackoverflow.com/help/accepted-answer), если вы сочтете это полезным. – dbush

+0

Арифметика указателя, такая как 'char * p = (char *) 0; a = (p + 1); '_is_ неопределенное поведение. Требование «этот код, который не вызывает UB:», в лучшем случае неверно. Арифметика указателя действительна только на адресе законного объекта (и 1 объект передает это). 'NULL' не является адресом законного объекта. – chux

1

Добавление 1 к указателю продвигает указатель на следующий адрес, соответствующий типу указателя.

Когда указатели (null) + 1 преобразуются в int, вы эффективно печатаете размер каждого из типов, на которые указывают указатели.

printf("%d %d %d %d\n", sizeof(char), sizeof(int), sizeof(float), sizeof(double));

делает в целом то же самое. Если вы хотите, чтобы увеличить каждый указатель только 1 BYTE, вам нужно, чтобы бросить их (символ *) перед увеличением их, чтобы компилятор знать

Поиск информации о арифметику указателей, чтобы узнать больше.

+0

О, теперь я понял. благодаря ! –

+0

'% d' ожидает аргумент' int'; 'sizeof' дает результат' size_t'. Используйте '% zu' (или добавьте в' int', если у вас есть старая реализация, которая не поддерживает '% zu'). И вы не говорите, что поведение не определено. –

0

Вы типизируете указатели на примитивные типы данных, а затем набираете их самим указателям, а затем используете оператор * (косвенный) для косвенного значения этой переменной. Например, (int)(p + 1); означает p; указатель на константу, сначала увеличивается на следующий адрес внутри памяти (0x1), в этом случае. и чем это 0x1 приложено к int. Это имеет смысл.

Смежные вопросы