Страницы

Поиск по вопросам

среда, 27 ноября 2019 г.

Как узнать, можно ли разыменовать указатель?

#c++ #c


У меня возникла некоторая проблема: у меня в одной части программы используется объдинение
из указателя char* и просто char. Хотелось бы вывести оба этих элемента (по-разному
их интерпретируя, разумеется). Но, если в объединении хранится символ, указатель разыменовывать
нельзя — иначе упадём. Как заранее выяснить, не приведёт ли разыменовывание к сегфолту?

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


Ответы

Ответ 1



Мне кажется, что абсолютно валидного, в 100% случаев работающего решения нет, кроме введения какого-то дополнительного флага, о чем Вы уже сказали.

Ответ 2



Можно проверять, доступен ли адрес по чтению-записи, но делать выводы о том, что в Вашем union хранится все же сложно. На всякий случай, код, который я использовал в Linux (и как ни странно, проверял в MinGW Windows-XP) /* misc debug */ #include #include #include #ifndef unix #define sigjmp_buf jmp_buf #define siglongjmp longjmp #define sigsetjmp(buf,flag) setjmp(buf) #endif #ifdef __cplusplus static sigjmp_buf _av_is_accessible_jmp; static void _av_is_accessible_hdr (int sig) { siglongjmp(_av_is_accessible_jmp, 0); } #endif // check is addr readable/writable, returns 1 if so static int _av_is_mem_accessible (volatile char *addr, int writable) { if (addr == 0 || addr == (void *)-1LL) return 0; int rc = 1; #ifndef __cplusplus sigjmp_buf _av_is_accessible_jmp; void _av_is_accessible_hdr (int sig) { siglongjmp(_av_is_accessible_jmp, 0); } #endif void #ifdef unix (* sigbus)(int) = signal(SIGBUS, _av_is_accessible_hdr), #endif (* sigsegv)(int) = signal(SIGSEGV, _av_is_accessible_hdr); #ifdef DEBUG #ifdef unix if (sigbus == SIG_ERR) { fputs ("SIG_ERR signal SIGBUS\n", stderr); exit(1); } #endif if (sigsegv == SIG_ERR) { fputs ("SIG_ERR signal SIGSEGV\n", stderr); exit(1); } #endif if (sigsetjmp(_av_is_accessible_jmp, 1)) { // MANDATORY save sigmask rc = 0; errno = EINVAL; } else { char t = *addr; if (writable) *addr = t; } #ifdef unix signal(SIGBUS, sigbus); #endif signal(SIGSEGV, sigsegv); return rc; } Идея тут состоит в том, что мы пытаемся прочесть байт по заданному адресу и если удалось, то записать его обратно. При неудаче перехватываем сигнал и возвращаем соответствующий результат. P.S. В gcc код реентерабельный и по идее thread-safe, а вот для крестов сделать такое не удалось...

Ответ 3



Вот есть один способ, правда, он непереносим и будет работать лишь на Linux. Нужно посмотреть на таблицу памяти, выделеной процессу. Если область памяти, на которую указывает указатель, помечена как доступная для чтения, значит, оный указатель можно разыменовать, и вывести содержимое той памяти. Если же нужно записать что-то в эту память, следует смотреть на второй флаг. Конечно, такой вариант скорее подходит лишь для целей отладки. Но, возможно, и на других системах можно как-нибудь выяснить корректность указателя. Идея кода была взята из исходников утилиты pmap. Подробнее о формате файла /proc/self/maps можно почитать в справочном руководстве proc(5). bool can_i_read(void* p) { uintptr_t begin, end; char readable, writable, executable, mapped; FILE* fp = fopen("/proc/self/maps", "r"); if (!fp) { return false; } while (fscanf(fp, "%" SCNxPTR "-%" SCNxPTR " %c%c%c%c", &begin, &end, &readable, &writable, &executable, &mapped) == 6) { if (begin <= (uintptr_t)p && (uintptr_t)p < end) { fclose(fp); // если нужно проверить доступность на запись, // следует смотреть флаг writable return readable == 'r'; } // не зациклимся — перед концом всегда будет перевод строки while (fgetc(fp) != '\n') ; } fclose(fp); return false; } Работоспособность проверена на Ubuntu 14.04.4 / Linux 4.1.15

Ответ 4



Как минимум в Windows первые 64 килобайта адресного пространства отводятся специально под обработку разыменования нулевого указателя. А char - это один байт, поэтому значение в нём будет меньше 256. В таком случае при записи следует обеспечить, чтобы обнулялись остальные байты и проверять, не превосходит ли значение 255. Приведение указателя к int будет некорректным в 64-битных программах, поэтому надо 256 приводить к void *, либо использовать нечто вроде uintptr_t.

Ответ 5



Если программа будет работать только на микроконтролерах то можно разименовывать любой указатель, просто будут возвращены любые данные. Если программа работает в ОС не используя "грязные трюки", определить допустимый адрес памяти или не допустимый, внутри программы нельзя. Только если это NULL.

Ответ 6



А нельзя ли проверить так: #include using namespace std; union chars { char c; char* p; }; void print(chars obj) { (uintptr_t)tmp = (uintptr_t)obj.p; if ((uintptr_t)obj.p > 255) { cout << obj.p[0] << endl; } else { cout << obj.c << endl; } } int main() { chars c_1; chars c_2; c_1.c = 'a'; c_2.p = "bcde"; print(c_1); print(c_2); system("PAUSE"); } Привести к типу int, и если значение 0-255, то в объединении символ, если больше указатель. (Указатели с трехзначными значениями ЕМНИП значениями являются системными, и динамически выделяя память, как для строк, вы их не назначите). P.S. Решение является "костылем", подробности смотрите в комментариях.

Ответ 7



Пользуйтесь новыйми фитчами C11, а именно _Generic(): #define ischarprt(x) _Generic((x), \ char: 0, \ char *: 1, \ default: -1) После чего: char ch = 'c'; char *prt = "pointer"; int i = 0; if(ischarprt(prt) == 1){ //Если данные - указатель на char } if(ischarprt(ch) == 0){ //Если данные - char } Вместо чисел могут быть строки и символы. UPD: вот ответ на ваш вопрос - тут

Комментариев нет:

Отправить комментарий