2013-07-04 2 views
5

Я новичок в C++, и у меня проблема с пониманием некоторого кода.C++ static_cast от int * до void * to char * - можете ли вы помочь мне понять этот код?

У меня было упражнение, чтобы написать функцию, которая возвращает размер int, и не использовать sizeof() и reinterpret_cast. Кто-то дал мне решение, но я не понимаю, как это работает. Не могли бы вы помочь мне понять это? Это код:

int intSize() { 
    int intArray[10]; 
    int * intPtr1; 
    int * intPtr2; 

    intPtr1 = &intArray[1]; 
    intPtr2 = &intArray[2]; 

    //Why cast int pointer to void pointer? 
    void* voidPtr1 = static_cast<void*>(intPtr1); 

    //why cast void pointer to char pointer? 
    char* charPtr1 = static_cast<char*>(voidPtr1); 

    void* voidPtr2 = static_cast<void*>(intPtr2); 
    char* charPtr2 = static_cast<char*>(voidPtr2); 

    //when I try to print 'charPtr1' there is nothing printed 
    //when try to print charPtr2 - charPtr1, there is correct value shown - 4, why? 
    return charPtr2 - charPtr1; 
} 

Чтобы подвести итог, что я не понимаю, почему мы должны изменить int* к void* и затем char*, чтобы выполнить эту задачу? И почему мы получаем результат, когда мы вычитаем charPtr2 и charPtr1, но при попытке распечатать только charPtr1 ничего не отображается?

+0

См http://stackoverflow.com/questions/3238482/pointer-subtraction-confusion –

+0

'char' - это размер' 1', поэтому приведение в 'char *' - это так, что вы можете подсчитать, сколько «символов» вписывается в один «int». – juanchopanza

+0

@PeterT Стандарт требует, чтобы 'char' был одним байтом. (Конечно, реализация получает возможность выбирать, сколько бит в байте.) –

ответ

4

Это важный бит:

intPtr1 = &intArray[1]; 
intPtr2 = &intArray[2]; 

Это создает два указателя на соседние Интс в массиве. Расстояние между этими двумя указателями - это размер целого числа, которое вы пытаетесь извлечь. Однако способ, с помощью которого выполняется арифметика указателей, заключается в том, что если вы вычтите эти два, то компилятор вернет вам размер в терминах int, который всегда будет равен 1.

Итак, что вы делаете дальше, это их повторное кастинг указатели символов. Символы являются (или де-факто) 1 байт каждый, поэтому разница между этими двумя указателями в качестве указателей на символы даст вам ответ в байтах. Вот почему вы бросаете указатели на символы и вычитаете.

Что касается void* - это необходимо избегать использования reinterpret_cast. Вам не разрешено вводить непосредственно с int* в char* с static_cast<>, но переход через void* удаляет это ограничение, так как компилятор уже не знает, что он начался с int*. Вместо этого вы можете просто использовать C-стиль, (char*)(intPtr1).

+1

Я получаю «invalid static_cast» от типа «int *», чтобы набирать «char *», когда статическое литье непосредственно на char *, поэтому кажется, что это недопустимо. – Alex1985

+0

Да, я только что попробовал это сейчас. Это то, что ограничение на 'reinterpret_cast <>' в вопросе для я вижу сейчас! – Rup

+0

Yup: static_cast от int * до char * не допускается. Необходимо пройти через void *. См. Мой ответ. – Bathsheba

14

Прежде всего, никогда не делайте этого в реальном коде. Вы сожжете свою ногу, выглядите как идиот, и все классные дети будут смеяться над вами.

Это, как говорится, вот как это работает: Основная идея состоит в том, что размер int равен смещению между двумя элементами в массиве int в байтах. Ints в массиве плотно упакованы, так что начало второго междунар приходит сразу после окончания первой:

int* intPtr1 = &intArray[0]; 
int* intPtr2 = &intArray[1]; 

Проблема здесь состоит в том, что при вычитании двух указателей Int, вы не получите разницу в байтах, но разница в ints. Таким образом, intPtr2 - intPtr1 - 1, потому что они 1 int друг от друга.

Но мы находимся на C++, поэтому можем наводить указатели на что угодно! Поэтому вместо использования указателей int мы копируем значение в указатели на символы, размер которых равен 1 байт (по крайней мере, на большинстве платформ).

char* charPtr1 = reinterpret_cast<char*>(intPtr1); 
char* charPtr2 = reinterpret_cast<char*>(intPtr2); 

Разница charPtr2 - charPtr1 - это размер в байтах. Указатели все еще указывают на то же место, что и раньше (т. Е. Начало второго и первого int в массиве), но теперь разница будет рассчитываться в размерах char, а не в размерах int.

Поскольку учения не позволяли reinterpret_cast, вам придётся прибегнуть к другому трюку. Вы не можете static_cast от int* до char* напрямую. Это способ C++ защитить вас от выполнения чего-то глупого. Хитрость заключается в том, чтобы сбрасывать до void*. Вы можете указать static_cast любой тип указателя на void* и от void* до любого типа указателя.

+1

+1 за то, что все ответы будут практически бесполезны, учитывая надуманный вопрос. – Bathsheba

1

Прочтите это: богато прокомментировано.

int intSize() 
{ 
    int intArray[2]; // Allocate two elements. We don't need any more than that. 

    /*intPtr1 and intPtr2 point to the addresses of the zeroth and first array elements*/ 
    int* intPtr1 = &intArray[0]; // Arrays in C++ are zero based 
    int* intPtr2 = &intArray[1]; 

    /*Note that intPtr2 - intPtr1 measures the distance in memory 
     between the array elements in units of int*/ 

    /*What we want to do is measure that distance in units of char; 
     i.e. in bytes since once char is one byte*/ 

    /*The trick is to cast from int* to char*. In c++ you need to 
     do this via void* if you are not allowed to use reinterpret_cast*/ 

    void* voidPtr1 = static_cast<void*>(intPtr1); 
    char* charPtr1 = static_cast<char*>(voidPtr1); 

    void* voidPtr2 = static_cast<void*>(intPtr2); 
    char* charPtr2 = static_cast<char*>(voidPtr2); 

    /*The distance in memory will now be measure in units of char; 
     that's how pointer arithmetic works*/ 
    /*Since the original array is a contiguous memory block, the 
     distance will be the size of each element, i.e. sizeof(int) */ 
    return charPtr2 - charPtr1; 
} 
+0

Вам даже не нужны два элемента, одного хватит. И вам не нужен весь промежуточный 'static_cast'; использование 'reinterpret_cast' для перехода непосредственно из' int * 'в' char * 'будет работать. И вам действительно ничего не нужно, так как результаты гарантированно будут равны 'sizeof (int)'. –

+0

@James Kanze, одно из многих ухищрений в том, что вам не разрешено использовать reinterpret_cast. Поэтому я должен использовать static_cast. На самом деле результатом будет sizeof (int). – Bathsheba

1

Указатель Вычитание в C++ дает число элементов между заостренными к объектам. Другими словами, intPtr2 - intPtr1 вернет число int между этими двумя указателями. Программа хочет знать количество байтов (char), поэтому преобразует int* в char*. По-видимому, автор не тоже хочет использовать reinterpret_cast. И static_cast будет не допускать прямого перехода от int* к char*, поэтому он проходит через void* (что разрешено).

Сказав все, что: судя по имени функции и как указатели на самом деле инициализированы, гораздо проще реализации этого будет:

int 
intSize() 
{ 
    return sizeof(int); 
} 
1

Там фактически нет необходимости конвертировать в void*, за исключением исключения reinterpret_cast.

Преобразование указателя-to-int к указателю-to char можно сделать в одном шаге с reinterpret_cast, или C-стиль литья (который, по стандарту, заканчивает тем, что делает reinterpret_cast). Вы можете сделать листинг C-стиля напрямую, но в этом контексте (по стандарту) находится a reinterpret_cast, в этом контексте вы нарушите требования. Очень сложно!

Однако, вы можете конвертировать из int* в char* через void* посредника, используя только static_cast. Это небольшое отверстие в системе типа C++ - вы делаете двухэтапный reinterpret_cast, даже не называя его, потому что для преобразования void* требуется специальное разрешение, которое должно быть выполнено через static_cast.

Так все void* вещи просто, чтобы избежать reinterpret_cast требование, и было бы глупо делать в реальном коде - зная, что вы можете сделать это может помочь понять, когда кто-то сделал это случайно в коде (т.е. ваш int*, кажется, указывает на строку: как это произошло? Ну, кто-то, должно быть, прошел через отверстие в системе типов. Либо C-образный литой (и, следовательно, reinterpret_cast), либо он должен пройти круглый void* через static_cast).

Если мы проигнорируем эту гимнастику, у нас теперь будет массив int. Мы берем указатели на смежные элементы. В C++ массивы упаковываются, причем разница между смежными элементами равна sizeof элементам.

Затем мы конвертируем эти указатели в указатели-char, потому что мы знаем (по стандарту), что sizeof(char)==1. Мы вычитаем эти указатели char, так как это говорит нам, сколько между ними кратных-sizeof(char) (если мы вычитаем указатели int, мы получим, сколько между ними кратных-sizeof(int)), которое в конечном итоге составляет размер int.

Если попытаться напечатать charPtr1 через std::cout, std::cout предполагает, что наша char* является указателем-to \0 -завершённый-буфер-of char, из C/C++ конвенции. Первый char указал на \0, поэтому std::cout ничего не печатает. Если бы мы хотели напечатать значение указателя char*, нам нужно было бы его отличить от void* (возможно, через static_cast<void*>(p)).

1

"не использовать SizeOf() и reinterpret_cast" ... Ничего не сказано о StD :: numeric_limits, так что вы могли бы сделать это так :)

#include <limits> 

int intSize() 
{ 
    // digits returns non-sign bits, so add 1 and divide by 8 (bits in a byte) 
    return (std::numeric_limits<int>::digits+1)/8; 
} 
+0

Симпатичный, но стандарт не говорит, что 'int' должен быть эффективно сохранен. Они могут сделать 'sizeof (int)' be 100, но имеют только 15 двоичных цифр точности. – Yakk

+2

Разве это не сломается на действительно неясной архитектуре; двоично-кодированное десятичное число, например? – Bathsheba

+0

@Yakk Согласно [cppreference] (http://en.cppreference.com/w/cpp/types/numeric_limits/digits) по крайней мере, 'nl :: цифры * определены * как' CHAR_BIT * sizeof (int) -1' (хотя я его не проверял). –