Как проверить тип в C в функции - PullRequest
0 голосов
/ 16 ноября 2010

Я делаю функцию, которая принимает два указателя на строки в качестве аргументов. Работает нормально, если вы передаете ей действительные аргументыЯ хочу знать, как проверить, что эти указатели действительны, а не, например, два случайных целых числа.Как мне это сделать?

char ** LCS ( char * s1, char * s2)    //thats the function

...

LCS(0,0) //...awful crash.. How do I avoid it?

Ответы [ 9 ]

2 голосов
/ 16 ноября 2010

С документацией и следуя девизу C: «Доверяй программисту».

/* s1 and s2 must be both valid pointers to null-terminated strings
** otherwise the behaviour is undefined */
char ** LCS ( char * s1, char * s2);
2 голосов
/ 16 ноября 2010

В теле функции проверьте:

if ((s1==NULL) || (s2==NULL)) {
  /* Do something to indicate bad parameters */
}
1 голос
/ 17 ноября 2010

Имеет ли смысл кто-то вызывать вашу функцию с NULL аргументами?Если нет, вам следует запретить NULL аргументы в контракте вашей функции, например, добавив комментарий над объявлением, в котором говорится, что он работает только с действительными, не NULL аргументами.Другими словами, любой, кто использует вашу функцию, соглашается не давать NULL аргументов;тогда их обязанность проверять это, а не ваше.

Если имеет смысл, чтобы один или оба аргумента были NULL, тогда вам нужно решить, какФункция ведет себя в таком случае и реализует это таким образом.В этом случае вы соглашаетесь поддерживать NULL аргументы и делать что-то разумное с ними, и поэтому становится вашей обязанностью проверить это и действовать соответственно (например, if (s1 == NULL)).

Если вы не можете придумать какое-либо разумное поведение для NULL аргументов, то выберите первый вариант и полностью запретите их.Если вы сделаете это, то ваш пример вызова LCS(0,0); нарушает контракт (т. Е. Передает NULL указатели, когда функция не соглашается их принимать) и должен быть удален.В более сложном сценарии, если вы передаете аргументы из переменных, и есть вероятность, что эти переменные указывают на NULL, то вы должны проверить перед вызовом LCS, например, if (v1 && v2) { LCS(v1,v2); } else { … }.

To track возможные ошибки, связанные с этим, вы можете использовать assert для проверки, например:

#include <assert.h>

char **LCS (char *s1, char *s2) {
    assert(s1);
    assert(s2);
    …
}

Это приведет к выходу из вашей программы, если s1 или s2 равно NULL, если NDEBUG не было определено до включения assert.h (в этом случае утверждения ничего не делают ).Таким образом, утверждения являются способом проверки в процессе разработки, что вызывающий не дает вам NULL аргументов, но это все равно ошибка, если они это делают.

Что касается других недействительных указателей,Вы не можете даже проверить достоверно, например, нет способа узнать, есть ли у вызывающей стороны действительно странная строка или они просто передали неправильный адрес.Это также их обязанность избегать, и LCS должен просто предполагать, что вызывающий абонент предоставляет вам действительные данные.Конечно, если у вас есть дополнительные ограничения, например, максимальная длина строк аргумента, вы должны сделать эти ограничения понятными для вызывающей стороны (т. Е. Указать контракт для функции, «эта функция выполняет X [ваша ответственность как разработчика LCS] при условии»).что… [их обязанности как пользователя LCS] »).Это относится к всему программированию, например, стандарт C определяет, как должен использоваться сам язык и стандартные библиотечные функции (например, нельзя делить на ноль, строки аргументов для strcpy не могут перекрываться и т. Д.).

0 голосов
/ 17 ноября 2010

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

Вы можете проверить несколько недопустимых (для приложений, в любом случае) значений указателя, включая NULL и, возможно, даже любое значение, которое будет указыватьна первую страницу адресного пространства процессов (обычно оно не отображается и его можно с уверенностью считать недействительным).В некоторых системах есть другие страницы, которые никогда не отображаются (например, последняя страница).В некоторых системах также есть способы узнать карту памяти процесса (/ proc / self / maps под Linux), которую вы могли бы (с большим трудом) посмотреть и посмотреть, находится ли указатель в отображенной области с соответствующим доступом.

Если вы используете систему * nix, вы могли бы зарегистрировать обработчик сигнала для SIGSEGV, который вызывается, когда ваша программа пытается получить доступ к памяти, к которой она не должна обращаться.Тогда вы можете поймать это и с некоторой работой выяснить, что произошло.Другая вещь, которую вы могли бы сделать, - это вызвать системный вызов, который принимает указатель, и использовать указатели, которые вы передали в качестве аргументов, и посмотреть, не произойдет ли это (с errno == EFAULT).Это, вероятно, не очень хорошо, поскольку системные вызовы делают что-то кроме проверки памяти на наличие прав чтения и / или записи.Вы всегда можете записать первый байт, на который указывает указатель на / dev / null или / dev / zero (используя системный вызов write, а не функции stdio), чтобы определить, есть ли у вас права на чтение, но если вы читаете байт из / dev/ ноль или / dev / random в первом указанном байте (используя системные вызовы read, а не функции stdio), но если данные в этой области важны, вы бы перезаписали их байт.Если бы вы попытались сохранить копию этих данных в локальную переменную, чтобы вы могли восстановить ее после теста, вы могли бы вызвать ошибку, когда читали из нее в своей программе.Вы можете разработать и записать его, а затем прочитать обратно, чтобы проверить оба права доступа, но это становится сложным.

Лучше всего полагаться на пользователя вашей функции, чтобы выполнитьправильная вещь.

0 голосов
/ 17 ноября 2010

Во-первых, как все говорили:

  • Проверка параметров NULL

Все остальное - эвристика и тщательное программирование

Вы можете предоставить прототип функции своим абонентам и установить для предупреждений значение 11: (по крайней мере -Werror -Wall и -Wextra для gcc). Это приведет к ошибке компиляции, если будет передан параметр неправильного типа. Это не поможет, если вызывающая программа сначала преобразует свои параметры в char * s (например, LCS((char*)1, (char*)1 )

Вы можете вызвать strlen для своих аргументов, но если значения не NULL, но все еще недопустимые значения, strlen может завершиться сбоем.

Вы можете попытаться увидеть, находятся ли указатели в допустимых сегментах для программы. Это не портативно, и все же не надежно.

Итак, подведем итоги, проверьте NULL и установите предупреждения на 11. Это то, что делается на практике.

0 голосов
/ 17 ноября 2010

Идеология C вращается вокруг принципа «программист знает, что он (а) делает». Половина причины того, почему C такой легкий и быстрый, заключается в том, что он не выполняет такие проверки типов.

Если вам действительно необходимо выполнить такие проверки, вам лучше бы было использовать C ++, используя ссылки (которые гарантированно не являются нулевыми) вместо указателей.

0 голосов
/ 16 ноября 2010

Вы можете реализовать свою собственную проверку типов, используя такую ​​структуру, как эта. Но вы также можете просто использовать язык с соответствующей проверкой типов. :)

typedef struct Var {
    enum Type { int, ptr, float ... } type;

    union {
        int Int;
        void *Ptr;
        float Float;
        ...
    } data;
} Var; 
0 голосов
/ 16 ноября 2010

Лучшее, что вы можете сделать, это проверить на NULL (0). В противном случае нет стандартного способа узнать, допустимо ли значение, отличное от NULL. Могут быть доступны некоторые хаки для конкретной платформы, но в целом эта проблема решается с помощью документации и надлежащей гигиены управления памятью.

0 голосов
/ 16 ноября 2010

Боюсь, в Си вы должны быть осторожны и надеяться, что программисты знают, что делать.

В этом случае 0 (ноль, ноль, NULL) является допустимым входом для функции.

Обычно в этом случае вы, по крайней мере, защитите функцию, проверив, действителен ли ввод.

например ...

char** LCS (char *s1, char *s2 )
{
  if ( s1 == 0 )
    return ...;
  if ( s2 == 0 )
    return ...;

  if ( strlen( s1 ) == 0 )
     return ...

/// do something ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...