LINUX.ORG.RU

Как узнать на изменяемую ли память указывает переменная?

 


1

2

Вот такой код

	char *s = "change me :)";
	s[0] = 'k';
вызывает ошибку сегментации.

Внимание, вопрос: что в чёрном ящике
Есть ли способ узнать на какой «тип» памяти указывает переменная?
Ну, то есть, можно ли её менять или нет.

Вообще в том виде как это приведено, это забота программиста. "change me :)" имеет тип const char []. Более того, на это должен ругаться компилятор, так как переменная типа char * инициализируется указателем на const char.

Более интересен другой вопрос: как узнать, const char * s хранит указатель на динамически выделенную память, или на string constant из rodata (т. е. надо ли делать free(s))?

Kiborg ★★★ ()

Ну в принципе можно залезть в /proc/self/maps и посмотреть в какой регион попадает адрес. Но зачем, когда можно просто

char s[] = "change me :)";

mix_mix ★★★★★ ()
Ответ на: комментарий от Kiborg

и то и другое (вопрос ТС и ваш про free)

во время компиляции узнать нельзя. Только после линковки и дин.загрузки

в принципе если заранее знать формат и расписание памяти - то не вопрос :-)

MKuznetsov ★★★★★ ()
Ответ на: комментарий от Kiborg

«change me :)» имеет тип const char []. Более того, на это должен ругаться компилятор, так как переменная типа char * инициализируется указателем на const char.

gcc -Wall на это не ругается, видимо не должен.

Более интересен другой вопрос: как узнать, const char * s хранит указатель на динамически выделенную память, или на string constant из rodata (т. е. надо ли делать free(s))?

Да, да, да, вот именно это мне и надо.

LinuxUser ★★ ()
Ответ на: комментарий от MKuznetsov

и то и другое (вопрос ТС и ваш про free)

во время компиляции узнать нельзя. Только после линковки и дин.загрузки

Как?

LinuxUser ★★ ()
Ответ на: комментарий от mix_mix

Ну в принципе можно залезть в /proc/self/maps и посмотреть в какой регион попадает адрес

А попроще и покроссплатформеннее есть способ?

LinuxUser ★★ ()
Ответ на: комментарий от MKuznetsov

Тут не в компиляции и линковке вопрос возникает, а в рантайме. Грубо говоря, мне нужно написать что-то вроде следующего:

const char * s = "abcd";
if (<...>) {
    s = strdup("efgh");
}
if (not_constant(s)) { free(s); s = NULL; }

Kiborg ★★★ ()

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

anonymous ()
Ответ на: комментарий от LinuxUser
#include <stdlib.h>
#include <sysexits.h>

int main(int argc, char** argv)
{
        (void)argc;
        (void)argv;

        char* s __attribute__((unused)) = "change me";

        exit(EX_OK);
}
[~]$ gcc -std=c99 test.c -Wwrite-strings  
test.c: In function ‘main’:
test.c:9:36: warning: initialization discards ‘const’ qualifier from pointer target type [enabled by default]
  char* s __attribute__((unused)) = "change me";
post-factum ★★★★★ ()
Ответ на: комментарий от LinuxUser

Как?

Читаешь оттуда один байт и тут же пишешь его обратно. Поймал signal 11 — not readable/writeable. Прошло успешно — можно писать. Ознакомься с

man 2 signal (или man sigaction) man sigsetjmp man siglongjmp

Если не знаешь, что с этим делать, то вот намек:

static jmp_buf test_env;

sigsegv_handler() {
  siglongjmp(test_env, 1);
}

set sigsegv handler
if (sigsetjmp(test_env) == 0) {
  do-stuff-with-memory;
  memory_is_writeable = true;
} else {
  memory_is_writeable = false;
}

kawaii_neko ★★★ ()

char *s = «change me :)»;

Твои ошибки начинаются с неверного типа. Const куда дел?

tfs ()
Ответ на: комментарий от LinuxUser

gcc -Wall на это не ругается, видимо не должен.

Вот этот момент меня заинтересовал. Нашел следующее:

1. http://c-faq.com/decl/strlitinit.html

2. Из man gcc (у меня версия 4.8.2):

-Wwrite-strings
           When compiling C, give string constants the type «const
           char[length]» so that copying the address of one into a non-«const»
           «char *» pointer produces a warning.  These warnings help you find
           at compile time code that can try to write into a string constant,
           but only if you have been very careful about using «const» in
           declarations and prototypes.  Otherwise, it is just a nuisance.
           This is why we did not make -Wall request these warnings.

           When compiling C++, warn about the deprecated conversion from
           string literals to «char *».  This warning is enabled by default
           for C++ programs.
Где-то видел сообщение, что в 4.5 это было включено по умолчанию.

Kiborg ★★★ ()
Ответ на: комментарий от kawaii_neko

Читаешь оттуда один байт и тут же пишешь его обратно.

Выглядит как костыль. С сигналами работать умею.

LinuxUser ★★ ()
Ответ на: комментарий от Kiborg
[~]$ gcc --version
gcc (GCC) 4.8.3 20140911 (Red Hat 4.8.3-9)
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
post-factum ★★★★★ ()
Ответ на: комментарий от Kiborg

в 4.5 это было включено по умолчанию.

Я наврал, было включено для g++. То есть оно и сейчас так:

$ echo -e "#include <stdio.h>\nint main(void){char * s; s = \"string constant\"; s[1]='p'; printf(\"%s\\\\n\",s); return 0;}" | g++  -x c++ -
<stdin>: In function ‘int main()’:
<stdin>:2:28: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]

Kiborg ★★★ ()
Ответ на: комментарий от post-factum

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

anonymous ()
Ответ на: комментарий от Kiborg

Нет, посмотри внимательно, у меня как раз указан -Wwrite-strings.

post-factum ★★★★★ ()
Ответ на: комментарий от anonymous

Какой ты остроумный и жирный. Как Петросян в сливочном масле.

post-factum ★★★★★ ()
Ответ на: комментарий от Kiborg

Вот именно. Кстати, странно, что его по умолчанию не включают, оно и правда помогает. Я вообще делаю так:

-std=c99 -D_DEFAULT_SOURCE -D_GNU_SOURCE -W -Wall -Wextra -pedantic -Wwrite-strings -Winit-self -Wcast-qual -Wpointer-arith -Wstrict-aliasing -Wformat=2 -Wmissing-declarations -Wmissing-include-dirs -Wno-unused-parameter -Wuninitialized -Wold-style-definition -Wstrict-prototypes -Wmissing-prototypes
post-factum ★★★★★ ()
Ответ на: комментарий от post-factum

Ага, strict aliasing тоже полезная вещь, особенно когда с оптимизациями собирать надо. Зато почему-то unused parameter в -Wextra включается.

Kiborg ★★★ ()

1. берёш адресс как «*void»

в контролируемом окружение(т.е поставив свои обработчики на всякие прерывания mmu) сохранив значение , пытаешься в него писать

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

а вообще что ты хочешь в С не регламентированно как выше уже сказали (в старых версиях строки были писабельны, ща не)

вообще в современных вм явный тренд от машин фон-неймана(где всё данные-код) к манчестерам(данных и код отдельно)

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

а вообще хороший твой ответ компиляешь сырец своим сырцом и второй запускает первый если упало(по сегментфалт) ридонли переменая если штатно то ок .

т.е если тестировать компилятор то это код компилирующий тесты компилятору и запускающее и анализирующее ответы :)

qulinxao ★★☆ ()
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int is_writeable(void *p)
{
        int fd = open("/dev/zero", O_RDONLY);
        int writeable;

        if (fd < 0)
                return -1; /* Should not happen */

        writeable = read(fd, p, 1) == 1;
        close(fd);

        return writeable;
}

int main()
{
        char *s = "change me :)";
        int can_write;

        can_write = is_writeable(s);
        if (can_write) {
                printf("can write\n");
                s[0] = 'k';
        } else {
                printf("write protected\n");
        }

        return 0;
}

Первая ссылка на SO

http://stackoverflow.com/a/14437277

Deleted ()
Ответ на: комментарий от unt1tled

Это шоб компилер не ругался что они не юзаются.

tfs ()
Ответ на: комментарий от post-factum

Как Петросян в сливочном масле.

К слову, ты не тот Петросян, который наложил три чужих патча на ядро и назвал это своим «форком»?

tfs ()
int main() {
    char * foo = "bar";
    foo[0] = 'g';
    return 0;
}
gcc -Wall -Wextra -pedantic

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

hlebushek ()

вызывает ошибку сегментации.

А еще на этапе компиляции компилятор должен был предупредить про отсутствие const.

andreyu ★★★★★ ()
Ответ на: комментарий от hlebushek

«bar» будет находится в data-сегменте, который rw, все корректно

Вам еще, наверное, и проверку индексов в массивах подавай =)

frame ★★★ ()
Ответ на: комментарий от hlebushek

По-моему это очень странно и неправильно.

Нет, это просто С. Тут все такое.

AptGet ★★★ ()
Ответ на: комментарий от post-factum

5.1.2.2.1 Program startup

5.1.2.2.3 Program termination

Не вполне стандартно для переменных, но и не gcc-изм:

6.8.3 Expression and null statements EXAMPLE 1

motto ()

Я тут вот что придумал:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

extern char etext, edata, end;

int main(int argc, char *argv[]) {
	char *s = "default";

	if (argc > 1)
		s = strdup("new");

	if (s < &etext || &edata < s)
		free(s);
		
	return EXIT_SUCCESS;
}
Кароче, если указатель указывает на какой-то адрес из сегмента с инициализирующими данными, то мы его не трогаем.
Список использованной литературы:
Статья о Data segment в Википедии
man 3 edata

Что думает по этому поводу здешняя публика?

LinuxUser ★★ ()
Последнее исправление: LinuxUser (всего исправлений: 1)

Не через жопу - никак

Через жопу - изменяемые переменные в линуксах обычно лежат сразу после кода.

sbrk(0) вызванный в самом начале main даст тебе указатель на границу между кодом и данными. Это не избавит от косяков в сошниках или mmap-ах.

sergej ★★★★★ ()
Ответ на: комментарий от sergej

Да, глобальные переменные с sbrk тоже попадут в read-only.

sergej ★★★★★ ()
Ответ на: комментарий от sergej

Ну или как вариант - слинковаться с какой-нибудь libelf и получить нужные данные из argv[0] :)

sergej ★★★★★ ()
Ответ на: комментарий от TrueTsar1C

Зачем? В чем смысл?

Хорошо, расскажу, как я до такой жизни докатился. У меня есть модуль который отвечает за конфиг. Для него заданы перечисление и массив из структур:

enum {
	CFG_USERNAME,
	CFG_PASSWORD,
	…,
	CFG_N /* length */
};

struct param_value {
	char *p;
	char *v;
} cfg[CFG_N];
Затем структура инициализируется значениями по-умолчанию:
struct param_value cfg[CFG_N] = {
	/* the order should be exact as in enum declaration */
	{ "username", "admin" },
	{ "password", "fig_vam" },
	…
};
Далее функция загружает из файла значения параметров и присваивает их полям v массива cfg с помощью вот такой функции:
static void config_add_param_value(const char *param, const char *value) {
	for (i=0; i<CFG_N; i++) {
		if (strcmp(cfg[i].p, param) == 0)
			cfg[i].v = strdup(value);
	}
	fprintf(stderr, "%s = %s\n", param, value);
}
Соответственно, если в файле конфига какой-нибудь параметр встречается два и/или более раза, то будет утечка памяти.
Но и free() просто так сделать нельзя, так как при первом вызове она будет пытаться освободить данные доступные только для чтения (из initialized data segment) и будет ошибка сегментации.
Можно, конечно, объявить char *config_defaults[CFG_N], заполнить его при первом вызове функции загрузки конфига и потом в config_add_param_value() делать free() если cfg[i].v == config_defaults[i].
Но это, на мой взгляд, не красиво.

LinuxUser ★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.